ホーム › シリアライズ・デシリアライズ › Ser-04
Ser-04: Java デシリアライズの脆弱性
Java の ObjectInputStream.readObject() は 便利ですが、信頼できないソースから受け取ったデータをそのままデシリアライズすると 深刻なセキュリティリスクになります。 このページでは脆弱性の仕組みと、ObjectInputFilter などの対策を解説します。
セキュリティリスク: 信頼できないデータのデシリアライズは危険です
信頼できないソース(ネットワーク・外部ファイルなど)から受け取ったバイト列をObjectInputStream.readObject() で読み込むと、 攻撃者が細工した「ガジェットチェーン」によって任意コードが実行される可能性があります(RCE: Remote Code Execution)。 2015 年には Apache Commons Collections の脆弱性(CVE-2015-7501)として大きく報告されました。
脆弱性の仕組みと対策
Java のデシリアライズは、バイト列からオブジェクトを復元するときに クラスのコンストラクタを経由せずに直接フィールドを設定します。 このため、クラスパス上に存在するクラスを組み合わせた「ガジェットチェーン」を バイト列に埋め込むことで、デシリアライズ時に任意のコードを実行させることができます。
| 対策方法 | Java バージョン | 概要 |
|---|---|---|
| resolveClass オーバーライド | Java 8+ | ObjectInputStream を継承してクラス名をホワイトリストで検証 |
| ObjectInputFilter | Java 9+(推奨) | setObjectInputFilter() でパターン文字列によるホワイトリスト設定 |
| sealed interface | Java 17+(設計レベル) | 許可する型をコンパイル時に明示して意図しないクラスを除外 |
| JSON への移行 | バージョン不問 | ObjectInputStream を使わず Jackson/Gson などで交換 |
サンプルコード
Java 8 版では resolveClass をオーバーライドしてホワイトリスト検証を実装しています。 Java 17 版では Java 9+ で追加された ObjectInputFilter を使った 簡潔なホワイトリスト設定を紹介します。 Java 21 版では sealed interface で デシリアライズ可能な型を設計レベルで制限し、パターンマッチング switch で型安全に処理します。
よくあるミス・注意点
ホワイトリストを設定しない ObjectInputStream は使用しない
通常の new ObjectInputStream(inputStream) は クラスパス上のあらゆるクラスをデシリアライズできます。 信頼できないソースからのデータには必ず ObjectInputFilter かresolveClass オーバーライドでフィルタリングしてください。
フィルターパターンの末尾に !* を忘れない
ObjectInputFilter.Config.createFilter() のパターンで 末尾に !* を付けないと、 ホワイトリストに含まれないクラスも許可されてしまいます。 必ず "com.example.SafeClass;java.lang.*;!*" のように末尾で全拒否してください。
Java バージョンごとの違い
ObjectInputFilter は Java 9 以降で利用できます。 Java 8 環境では ObjectInputStream を継承してresolveClass をオーバーライドする方法を使います。 Java 17 以降では ObjectInputFilter が推奨です。
テストする観点
- ホワイトリストに含まれるクラスは正常にデシリアライズできること
- ホワイトリストに含まれないクラスのデシリアライズで例外が発生すること(境界値:1クラスのみ許可した場合)
- ObjectInputFilter のパターンに
!*を含めないとフィルターが機能しないこと - シリアライズしたデータを改ざんした場合に
InvalidClassExceptionまたはStreamCorruptedExceptionが発生すること - Java 8 版の SecureObjectInputStream で、許可リスト外のクラス名を渡した場合に
InvalidClassExceptionがスローされること