サーバーに届いたデータを「そのままプログラムに渡している」だけで、リモートからサーバーを完全制御される——そんな攻撃が実際に起きています。
それが「安全でないデシリアライゼーション(Insecure Deserialization)」です。SQLインジェクションやXSSと比べると知名度は低いものの、OWASP Top 10(2021年版)にも選出されており、攻撃が成功すれば任意コード実行(RCE:リモートから任意のコマンドを実行できる状態)につながる非常に危険な脆弱性です。
この記事では、デシリアライゼーション攻撃の仕組みをゼロから丁寧に解説したうえで、JavaやPython・PHPなど代表的な言語での被害例、そして現場で今日から実践できる対策を網羅します。
安全でないデシリアライゼーションとは?
1. シリアライゼーション・デシリアライゼーションの基本
まず前提知識から整理しましょう。
・シリアライゼーション(直列化): プログラム内のオブジェクト(変数や設定情報のかたまり)をバイト列や文字列に変換し、ファイルやネットワーク越しに送れる形にする処理
・デシリアライゼーション(逆直列化): バイト列や文字列を受け取り、元のオブジェクトに復元する処理
たとえばWebアプリが「ユーザー設定」をクッキーやリクエストボディに持たせる場合、設定オブジェクトをシリアライズして送り、サーバー側でデシリアライズして使います。この仕組み自体は正常な動作です。
問題が起きるのは「外から受け取ったデータをそのまま信頼してデシリアライズする」ときです。
2. なぜ危険なのか
デシリアライゼーション処理は、受け取ったデータに書かれた「オブジェクトの型と中身」を忠実に復元しようとします。攻撃者はこれを逆用し、悪意あるオブジェクトを事前に細工したシリアライズデータとして送り込みます。
復元の過程でアプリが特定のクラスメソッド(コンストラクタや__wakeupなど)を自動的に呼び出すため、そこに仕掛けた処理が動いてしまいます。これを「ガジェットチェーン(gadget chain)」と呼び、連鎖的にシステムコマンドが実行されるRCEに至ることがあります。
攻撃の仕組み(敵を知る)
1. Javaのガジェットチェーン攻撃
最も有名な事例はJavaです。Apache Commons Collectionsライブラリの古いバージョンが持つクラスを悪用したガジェットチェーンが広く知られています。
攻撃の流れはおおよそ次のとおりです。
・Step 1: 攻撃者がツールで「特定ライブラリのクラスを使った悪意あるシリアライズデータ」を生成する
・Step 2: そのデータをWebアプリのリクエストパラメータやセッションデータとしてサーバーに送信する
・Step 3: サーバー側のObjectInputStreamがデシリアライズを実行し、ガジェットチェーンが動き出す
・Step 4: 最終的にRuntime.exec()などが呼ばれ、任意のOSコマンドが実行される
実際、Oracle WebLogic・Jenkins・JBossなど多くのJavaベースのアプリが、このパターンでRCE攻撃を受けた歴史があります。どれも「ガジェットとして使えるライブラリが存在していた」ことが根本原因です。
2. PHPのunserialize()悪用
PHPにも同様の問題があります。unserialize()関数にユーザー入力をそのまま渡すコードがあると、__wakeup()や__destruct()などのマジックメソッドが悪意ある形で呼ばれる可能性があります。
古いWordPressプラグインやCMSフレームワークで脆弱なコードが見つかった事例があり、SQLインジェクションと同程度の深刻度になるケースもあります。PHPのシリアライズ形式(O:4:"User":2:{s:4:"name";s:5:"admin";}のような文字列)がクッキーに見えたら要注意です。
3. Pythonのpickleモジュール
Pythonで広く使われるpickleモジュールは、シリアライズデータに「実行する命令」を埋め込める設計になっています。外部から受け取ったpickleデータをpickle.loads()で読み込むだけで、攻撃者が指定したコマンドがサーバーで動きます。
機械学習モデルの共有に.pkl形式がよく使われますが、「信頼できないソースのpickleファイルを読み込む」のは非常に危険です。公開されているモデルを利用する場合も出所を必ず確認しましょう。
具体的な防御手順
1. 外部からのシリアライズデータをデシリアライズしない設計にする
最もシンプルで確実な対策は「ユーザーやネットワークから受け取ったデータをデシリアライズしない設計にすること」です。
構造化データの受け渡しにはJSON・Protocol Buffersなど、コードを実行しない安全なフォーマットを使いましょう。JavaであればObjectInputStreamの代わりにJackson(JSONパーサー)、PythonであればJSONやYAML(タグ機能無効化)が選択肢になります。
# Python: pickle の代わりに json を使う(安全な設計) import json # NG: 信頼できないデータに pickle を使う # obj = pickle.loads(user_supplied_data) # 危険 # OK: JSON でデータ受け渡しする data = json.loads(user_supplied_json_string)
2. デシリアライズ対象のクラスをホワイトリストで制限する
どうしてもデシリアライズが必要な場合は、許可するクラスをホワイトリストで制限します。JavaではObjectInputStreamをサブクラス化してresolveClass()をオーバーライドし、許可リスト以外のクラスは例外で弾く実装が標準的な防御策です。
// Java: ObjectInputStream をサブクラス化してクラスを制限する例 public class SafeObjectInputStream extends ObjectInputStream { private static final Set
ALLOWED_CLASSES = Set.of( "com.example.UserSettings", "com.example.SessionData" ); @Override protected Class> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!ALLOWED_CLASSES.contains(desc.getName())) { throw new InvalidClassException("Unauthorized class: " + desc.getName()); } return super.resolveClass(desc); } }
3. 整合性チェック(HMACや電子署名)を付ける
クッキーやAPIパラメータとして渡すシリアライズデータには、改ざん検知のためにHMAC(ハッシュベースのメッセージ認証コード)や電子署名を付加します。検証前のデータはデシリアライズしないルールを徹底することで、細工されたデータを早期に弾けます。
4. サンドボックス・コンテナ分離で被害を局所化する
万一デシリアライゼーション攻撃が成功した場合でも、被害を最小限に抑えるための多層防御が重要です。
・Dockerコンテナ分離: 侵害されても他のシステムへの横展開を防ぐ
・SELinux/AppArmor: プロセスが実行できる操作をポリシーで絞る
・Javaセキュリティポリシー(カスタム実装): OSコマンド実行・ファイルアクセスを制限する
Linuxサーバー上でのSELinuxやAppArmorの設定については、姉妹サイトLinuxMaster.JPで詳しく解説しています。
5. 依存ライブラリを最新に保つ
ガジェットチェーン攻撃の多くは、脆弱なライブラリが存在することを前提にしています。使用するすべての依存ライブラリをSBOM(ソフトウェア部品表)で把握し、定期的にアップデートする運用が効果的です。
# Java(Maven): 依存ライブラリの脆弱性をスキャン mvn org.owasp:dependency-check-maven:check # Java(Gradle): 依存ツリーを確認 ./gradlew dependencies | grep "commons-collections" # Python: pip-audit で脆弱な依存関係を検出 pip install pip-audit pip-audit
中小企業でも今日からできること
「Javaアプリを運用していない自社には関係ない」と思うかもしれませんが、WordPressなどのPHPアプリを使っている場合も注意が必要です。以下を確認してみてください。
・WordPressプラグインの更新: 脆弱なunserialize()を含む古いプラグインが残っていないか確認する(Wordfence等のスキャナーが有効)
・クッキーの内容を確認する: ブラウザの開発者ツールでクッキーの値がBase64エンコードされたシリアライズデータに見えないか確認する。PHPシリアライズ形式(O:4:"User":2:{...}のような文字列)なら要注意
・脆弱性スキャンを定期実施: NiktoやOWASP ZAPで既知の脆弱なエンドポイントを検出する
・WAFを活用する: ModSecurityやクラウドWAFに、シリアライズデータの既知の悪意ある特徴を検知するルールを追加する
まず「自社のアプリがどこでデシリアライズ処理を行っているか」を開発者やSIベンダーに確認するところから始めましょう。
よくある誤解と注意点
「Base64やURLエンコードをかけているから安全」は誤り
エンコードは難読化であって暗号化ではありません。Base64は誰でも復元できますし、攻撃者はエンコードした悪意あるシリアライズデータをそのまま送ります。エンコードはデシリアライゼーション攻撃への防御になりません。
「内部ネットワークだから大丈夫」は危険な思い込み
内部ユーザーが悪意を持っていたり、フィッシングで侵害されたPCが踏み台になるケースがあります。内部向けシステムでも同様の対策を施すことが重要です。
「シリアライゼーションを使っていない」の盲点
開発者が意識していなくても、使用しているフレームワーク・ライブラリの内部でシリアライゼーションが行われていることがあります。フレームワークのセキュリティアドバイザリを定期的にチェックする習慣をつけましょう。
本記事のまとめ
安全でないデシリアライゼーションは、成功すれば任意コード実行(RCE)に至る深刻な脆弱性です。JavaだけでなくPHP・Pythonなど多くの言語に関係し、現代のWebアプリやサーバーサイドシステムが抱えるリスクの一つです。
| 対策 | 効果 | 難易度 |
|---|---|---|
| JSONなど安全なフォーマットに切り替え | 根本解決 | 中(設計変更が必要) |
| クラスのホワイトリスト制限 | 攻撃の大部分をブロック | 中 |
| HMAC・電子署名による整合性チェック | 改ざんデータを早期排除 | 低~中 |
| サンドボックス・コンテナ分離 | 被害の局所化 | 中 |
| 依存ライブラリの最新化 | ガジェットチェーンの封じ込め | 低(仕組み化すれば継続可) |
「自社のアプリはどのデータをデシリアライズしているか」を一度棚卸ししてみることが、セキュリティ強化の第一歩です。攻撃者は「デシリアライズしているだけ」という些細な処理を突いてきます。正しく知って、正しく備えましょう。
デシリアライゼーション攻撃から自社システムを守りたいですか?
「うちのコードは大丈夫か」と気になった方は、まず使用しているフレームワークのセキュリティアドバイザリを確認してみてください。
正しいセキュリティ知識を体系的に身につけたい方へ、メルマガで実践的なセキュリティ対策ノウハウをお届けしています。
