【デシリアライゼーション編】復元されるデータを信頼しない設計|CTF思考フレームワーク #29
こんにちは、アンペンです!
前回は、サーバ視点のSSRFを扱いました。
今回は、#15で扱ったデシリアライゼーションを、サーバ側のフレームワーク視点で改めて深く見ていきます。信頼できないオブジェクトがそのままRCEの鍵になる構造と、その守り方を学びましょう。

#15でデシリアライズって出てきたよね?なぜまた?

#15はWebアプリ視点。今回はサーバ側フレームワーク(Java・PHP・Pythonなど)のガジェットチェーンとフレームワーク事例を深掘りするよ。
デシリアライズは#15でも一度学びました。今回はその“続編”——同じ仕組みが、JavaやPHPといった本格的なサーバ・フレームワークの世界で、いかに繰り返し大事故を起こしてきたかを見ていきます。実はこのデシリアライズRCE、長年セキュリティ業界を悩ませてきた“常連”。なぜ何度も同じ穴が空くのか、そしてどうすれば根本から塞げるのかを、自動組立ロボットのたとえでほどいていきます。
信頼できないデータのデシリアライズは、復元中に既存クラスのメソッドが連鎖的に呼ばれ、最終的にRCEに到達します。Java・PHP・.NET・Pythonの主要フレームワークで重大CVEが繰り返し発生しており、守りは「外部入力をデシリアライズしない」「JSONや安全パーサに置換」「署名検証」の組み合わせです。
この記事で分かること
- デシリアライズがなぜRCEに到達するか(ガジェットチェーン)
- 言語/フレームワークごとの代表的な被害事例
- 外部入力デシリアライズを避ける設計の考え方
📖 はじめてのWebセキュリティ #29|デシリアライゼーションRCE編
『データ復元』だけでRCEに届く仕組みを、サーバ側フレームワーク視点で学びます。 シリーズ一覧を見る →
⚠️ 大事なお約束
この記事の確認は、CTF・自分の検証環境のみで行ってください。本番サービスにペイロードを送る行為は不正アクセスに該当します。
なぜ復元だけでRCEに到達するのか
シリアライズされたデータは、ただの『値の並び』ではありません。多くの言語では、復元時にクラスのコンストラクタ・特殊メソッド(例: Java の readObject、PHP の __wakeup、.NETの OnDeserialization)が自動的に呼ばれます。
攻撃者はこれを悪用し、既存のクラスをパズルのように連結して、最終的にコマンド実行や任意関数呼び出しに到達するペイロードを作ります。これがガジェットチェーンです。
ガジェットチェーンの発想は、ちょっとパズルみたいで面白い(そしてこわい)んです。攻撃者は、新しい悪意のコードを持ち込むわけではありません。あなたのアプリにもともと入っているライブラリの中から、“単体では無害な部品”を見つけ出し、ドミノのように連結する。Aの自動スイッチがBを呼び、BがCを呼び……最後にコマンド実行へ。それぞれは普通の機能なのに、つなぎ方しだいで凶器になる。だから「この部品は安全だから大丈夫」という単体の判断が、まったく通用しないんです。
おさらいすると、デシリアライズ(荷ほどき)は“ただ中身を取り出す”だけではありませんでした。多くの言語では、復元のときに『特殊なメソッド』が自動で動きます。たとえば“箱を開けたら自動で起動するスイッチ”がついているイメージ。Javaなら readObject、PHPなら __wakeup……名前は違っても、「復元=何かが自動実行される」という性質は共通です。攻撃者は、この“自動スイッチ”を悪用の足がかりにします。
図解:通常データ vs ガジェットチェーン入りペイロード

同じデシリアライズ処理に通すデータでも、ガジェットチェーンが仕込まれていると、復元工程の途中で攻撃が成立します。

工場の自動組立ロボットは『部品を順番にはめる』だけのはずです。でも、部品の一部に『隣のドリル機を起動して穴を開けて』という指示が混ぜられていたら、組立中にロボットがその通り動いてしまうかもしれません。デシリアライズも同じで、『復元』という工程そのものが、攻撃者の用意した指示の連鎖を実行する装置になります。
ここで覚える用語:ガジェットチェーン
既存ライブラリ内の『単独では無害なクラス』を、復元時に自動で呼ばれるメソッド経由で連結し、最終的に任意コード実行に至るチェーンです。Java の Apache Commons Collections、.NETの BinaryFormatter など、フレームワーク標準でガジェットが発見されてきました。
ここがこの攻撃の“しぶとさ”の理由です。攻撃者は、あなたが入れた覚えのある、ごく普通の有名ライブラリを“材料”にします。だから、自分のコードをいくら見直しても見つからない。しかも新しいガジェット(部品の組み合わせ)は今も発見され続けていて、「今は安全」が来年もそうとは限らない。いたちごっこになりがちなのは、悪意が“外から来る”のではなく、“手元の部品の組み合わせ方”から生まれるからなんです。
言語/フレームワークごとの代表例
やっかいなことに、このデシリアライズRCEは特定の言語だけの問題ではありません。Java、PHP、.NET、Python——主要な言語のほとんどに、それぞれ“やってはいけない復元関数”があり、どれも実際に大きなCVE(脆弱性)を生んできました。言語を変えれば解決、という話ではないんです。だからこそ、“どの言語でも共通して効く守り”を知っておくことが大切になります。
代表的な被害領域

- Java:
ObjectInputStream.readObject+ Apache Commons Collections等のガジェット。多くのRCE CVEが報告されている - PHP:
unserialize+ マジックメソッド(__wakeup/__destruct/__toString)経由のチェーン - .NET:
BinaryFormatter/NetDataContractSerializerでガジェット悪用、Microsoftが非推奨化 - Python:
pickle.loadsは仕様上__reduce__でコード実行が容易、原則使用禁止
これらは、メッセージキュー・キャッシュ・Cookie・セッションストアなど、内部間通信の信頼前提で使われがちな箇所に潜みます。『内部だから安心』の油断が大きな被害につながります。
この『内部だから安心』という油断、本当に多いんです。「外部から来るユーザー入力は警戒するけど、サーバ同士のやりとりや、自分が前に保存したセッションデータは信用していい」——つい、そう思ってしまいますよね。でも、Cookieもキャッシュもメッセージキューも、元をたどれば利用者が触れる可能性があります。“内部の通信だから”と無防備にデシリアライズした箇所が、実は外までつながっていた、というのが典型的な事故パターンです。
練習はシンプルで、コードベースを“grep(文字列検索)”して危険な復元関数を探すだけ。readObject、unserialize、pickle.loads……見つけたら、「そこに流れ込むデータは、どこから来ている?」とたどります。攻撃のためではなく“健康診断”ですね。もちろん本物のサービスにペイロードを送るのは厳禁。自分のコードとラボの中で点検しましょう。
CTFでやってみよう:危険関数の棚卸し
自分のラボのコードベースで、危険なデシリアライズ関数を洗い出そう
目的は実際にRCEを成立させることではなく、『外部入力がどこで復元されているか』を見つけることです。
- コードベースで
readObject/unserialize/BinaryFormatter/pickle.loads/Marshal.loadを文字列検索する - 各箇所の入力源(Cookie・キュー・キャッシュ・APIなど)を辿り、外部到達可否を整理する
- 同等の処理をJSONや安全な独自パーサに置き換えられるか検討する
- どうしても置換不可なら、署名(HMAC)検証や許可クラスのallow-listを追加する
- 監査ログでデシリアライズ実行・例外のパターンを残せるようにする
守り方には、実は“順位”があります。一番効くのは何か、会話で確かめましょう。

allow-list(許可するクラスの限定)を入れれば、危険な復元も安全に使えるんじゃない?

それも有効な“次善策”だけど、本命じゃないんだ。allow-listは「どのクラスまで復元してOK」のフィルターだけど、設定漏れや想定外のガジェットで破られることがある。だから一番強いのは、そもそも“言語固有の復元をやめて、JSONみたいな安全な形式に乗り換える”こと。#15でも言ったけど、JSONは“データしか運べない”から、そもそも自動スイッチを起動できない。フィルターで頑張るより、危ない仕組みを土俵から下ろすほうが確実だよ。
守る側なら、「外部入力をデシリアライズしない」
デシリアライズRCEの守りは、「外部から届くデータは言語固有シリアライズで復元しない」の一行に尽きます。代わりにJSONなど安全なフォーマットに移行し、必要なら署名でなりすましを防ぎます。
- 外部I/Fから届くデータはJSON・Protocol Buffersなど安全なフォーマットに統一
- どうしても言語固有シリアライズを使う場合はHMAC等で署名検証を必須にする
- 許可クラスのallow-list(JEP 290 / Filter, PHP unserialize options)を活用
- BinaryFormatter等の非推奨APIは計画的に置換する
- セッション・Cookieにオブジェクトを格納しない設計を選ぶ
- WAF・監査ログでデシリアライズ系の異常パターンを検知する

『内部だから安心』が一番危ない、ってことだね。

そう。デシリアライズ関数の利用箇所を棚卸しして、安全なフォーマットに置き換えるのが王道だよ。
ここまでをひと言で言うと、デシリアライズRCEの守りは『危ない復元を、そもそも使わない』。allow-listや署名は“どうしても使うときの保険”であって、本命はJSONなど安全な形式への移行です。手元の無害な部品が凶器に化ける——この攻撃の性質上、“部品を一個ずつ点検する”より“危ない箱を開けない”ほうが、ずっと確実なんです。
まとめ:『復元』は安全な形式に置き換える
- デシリアライズRCEはガジェットチェーンで復元中に成立する
- Java/PHP/.NET/Pythonでは長年の重大CVE領域
- 守りは外部入力をJSON等に置換+署名検証+allow-list
- セッション・Cookie・キューでの利用を棚卸し
今日の持ち帰りは『復元という地味な工程に、自動スイッチが潜んでいる』です。デシリアライズRCEは、コードを読んでも一見ふつうの処理に見えるのが怖いところ。だからこそ、危険な復元関数の使用箇所を一覧化し、外部に少しでもつながるものはJSONへ置き換える。“内部だから安心”を捨てることが、この古くて新しい攻撃への最良の備えになります。
次回は、シェル経由でOSコマンドが連結されるコマンドインジェクションを、サーバ視点で改めて深掘りします。
