Basic Java Deserialization with ObjectInputStream readObject
Tip
AWS Hackingを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hackingを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Az Hackingを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks Trainingの全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。
HackTricksをサポート
- subscription plans を確認してください!
- 💬 Discord group、telegram group に参加し、X/Twitterで @hacktricks_live をフォローするか、LinkedIn page と YouTube channel を確認してください。
- HackTricks と HackTricks Cloud の github repos に PR を送信して hacking tricks を共有してください。
このPOSTでは、java.io.Serializable を使った例と、受信ストリームが攻撃者に制御されている場合に readObject() をオーバーライドすることがいかに危険であるかを説明します。
Serializable
Java の Serializable インタフェース (java.io.Serializable) は、クラスがシリアライズおよびデシリアライズされるために実装しなければならないマーカーインタフェースです。Java オブジェクトのシリアライズ(書き込み)は ObjectOutputStream で行い、デシリアライズ(読み込み)は ObjectInputStream で行います。
Reminder: Which methods are implicitly invoked during deserialization?
readObject()– クラス固有の読み取りロジック(実装されていて private な場合)。readResolve()– デシリアライズされたオブジェクトを別のオブジェクトに置き換えられる。validateObject()–ObjectInputValidationコールバック経由。readExternal()–Externalizableを実装するクラス向け。- コンストラクタは実行されない – したがって gadget chains は前述のコールバックにのみ依存します。
そのチェーン内の任意のメソッドが攻撃者制御下のデータ(コマンド実行、JNDI lookups、reflection 等)を呼び出すと、デシリアライズ処理は RCE gadget になってしまいます。
Lets see an example with a class Person which is serializable. This class overwrites the readObject function, so when any object of this class is deserialized this function is going to be executed.
In the example, the readObject function of the class Person calls the function eat() of his pet and the function eat() of a Dog (for some reason) calls a calc.exe. We are going to see how to serialize and deserialize a Person object to execute this calculator:
The following example is from https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649
import java.io.Serializable;
import java.io.*;
public class TestDeserialization {
interface Animal {
public void eat();
}
//Class must implements Serializable to be serializable
public static class Cat implements Animal,Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
//Class must implements Serializable to be serializable
public static class Dog implements Animal,Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
//Class must implements Serializable to be serializable
public static class Person implements Serializable {
private Animal pet;
public Person(Animal pet){
this.pet = pet;
}
//readObject implementation, will call the readObject from ObjectInputStream and then call pet.eat()
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
// Example to call Person with a Dog
Animal animal = new Dog();
Person person = new Person(animal);
GeneratePayload(person,"test.ser");
payloadTest("test.ser");
// Example to call Person with a Cat
//Animal animal = new Cat();
//Person person = new Person(animal);
//GeneratePayload(person,"test.ser");
//payloadTest("test.ser");
}
}
結論(古典的なシナリオ)
この非常に基本的な例が示すように、ここでの「脆弱性」は readObject() メソッドが 攻撃者が制御する他のコードを呼び出している ために発生します。実際の gadget chains では、外部ライブラリに含まれる数千のクラス(Commons-Collections, Spring, Groovy, Rome, SnakeYAML, など)が悪用され得ます — 攻撃者はコード実行を得るために到達可能な 1つ の gadget があれば十分です。
2023-2025: 実際の Java デシリアライズ脆弱性で何が変わったか?
最近の事例は、ObjectInputStream のバグがもはや単に「レガシーな HTTP エンドポイントに .ser ファイルをアップロードするだけ」ではないことを思い出させます:
- Broker / queue consumers: Spring-Kafka (
CVE-2023-34040) は、consumer が異常なcheckDeserExWhen*フラグを有効にしている場合、攻撃者が制御するトピックからの例外ヘッダをデシリアライズするだけで十分であることを示しました。 - Client-side trust of remote servers: Aerospike Java client (
CVE-2023-36480) はサーバから受信したオブジェクトをデシリアライズしました。ベンダーの対応は注目に値します: 新しいクライアントは、弱いフィルタで維持しようとする代わりに、Java runtime serialization/deserialization サポートを削除しました。 - “Restricted” streams are often still too broad:
pac4j-core(CVE-2023-25581) はRestrictedObjectInputStreamでデシリアライズを保護しようとしましたが、許可されたクラス集合は依然として gadget の悪用が可能なほど十分に大きかったです。
攻撃的な教訓は、危険な信頼境界はしばしば「ユーザーがブロブをアップロードする」ではなく、「開発者が信頼していると考えたあるコンポーネントが、最終的に readObject() に到達するストリームにバイトを注入できる」ことだという点です。
本格的な gadget 調査に時間をかける前に低ノイズの到達性チェックが必要な場合は、専用の Java ページを使用してください:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
readObject() が依然として gadget のエントリポイントを作るアンチパターン
クラス自体が明らかな RCE gadget でなくても、攻撃者制御のオブジェクトがグラフに埋め込まれている場合、以下のパターンだけで悪用可能になります:
readObject()からオーバーライド可能なメソッドやインタフェースメソッドを呼び出す(pet.eat()は上の PoC の典型例)。- デシリアライズ中にルックアップ、リフレクション、クラスローディング、式評価、または JNDI 操作を行うこと。
- 攻撃者制御のコレクションやマップをイテレートすること。副作用として
hashCode()、equals()、comparators、transformers がトリガーされる可能性がある。 - 危険な後処理を行う
ObjectInputValidationコールバックを登録すること。 - 「private
readObject()」が十分な保護だと仮定すること。これはディスパッチの意味論を制御するだけで、デシリアライズを安全にするものではありません。
導入すべき最新の緩和策
- JEP 290 / Serialization Filtering (Java 9+)
承認リストと明示的なグラフ制限を使用してください:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
- Apply a filter on every untrusted stream, not just globally:
try (var ois = new ObjectInputStream(input)) {
var filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;!*"
);
ois.setObjectInputFilter(filter);
return (Message) ois.readObject();
}
- JEP 415 (Java 17+) Context-Specific Filter Factories
同一 JVM が複数のデシリアライズコンテキスト(RMI、cache replication、message consumers、admin-only imports)を持ち、それぞれが別個の allow-list を必要とする場合はこれを優先してください。 - Keep
readObject()boringdefaultReadObject()/明示的なフィールド読み取りのみを行い、その後厳格な不変条件チェックを行ってください。I/O、攻撃者制御のオブジェクトをデリファレンスするログ、動的ルックアップ、またはデシリアライズされたサブオブジェクトへのメソッド呼び出しは行わないでください。 - If possible, remove Java native serialization from the design
Aerospike の修正は良いモデルです。機能が必須でない場合、readObject()/writeObject()の使用を削除する方が、完璧なフィルタを永続的に維持しようとするより安全なことが多いです。
検出と調査のワークフロー
ysoserialは gadget の検証と素早い RCE/URLDNS プローブのベースラインであり続けます。marshalsecは sink が JNDI/LDAP/RMI の領域にピボットする場合に依然役立ちます。GadgetInspectorはターゲットの JAR を持っていて、アプリケーション固有の gadget chains を探す必要がある場合に有用です。- Java 17 は
jdk.DeserializationFlight Recorder イベントを追加しました。これはObjectInputStreamが実際にどこで使われているか、フィルタが適用されているかを確認するのに有用です。
安全な readObject() 実装のためのクイックチェックリスト
- メソッドを
privateにし、シリアライズフックに@Serialを注釈して、コンパイラが誤ったシグネチャを検出できるようにする。 - フルオブジェクトグラフを手動で読み取る強い理由がない限り、まず
defaultReadObject()を呼び出す。 - 検証されるまでは、すべてのネストされたオブジェクトを攻撃者制御と見なす。
readObject()内からデシリアライズされた協調オブジェクトのメソッドを呼び出してはいけない。- コードレビューを
ObjectInputFilterのレビューと組み合わせて行うこと;ストリームが依然として任意のクラスを受け入れる場合、「安全に見えるreadObject()コード」だけでは不十分です。
References
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
AWS Hackingを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hackingを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Az Hackingを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks Trainingの全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。
HackTricksをサポート
- subscription plans を確認してください!
- 💬 Discord group、telegram group に参加し、X/Twitterで @hacktricks_live をフォローするか、LinkedIn page と YouTube channel を確認してください。
- HackTricks と HackTricks Cloud の github repos に PR を送信して hacking tricks を共有してください。


