PR 本記事には広告(Amazonアソシエイト・もしもアフィリエイト・A8.net等)が含まれます。掲載情報の正確性には努めていますが、商品の詳細は必ずリンク先で最新情報をご確認ください。
CTF・セキュリティ学習

【デシリアライズ編】信頼できないデータを蘇らせるな|CTF思考フレームワーク #15

かも次郎とアンペンが「デシリアライズ」を解説するマスコットイラスト
安全に生きたい編集部

こんにちは、アンペンです!

前回は、テンプレートに混ざる悪意でRCEに至るSSTIを扱いました。

今回は、データを「オブジェクトとして復元する」処理が攻撃に化けるデシリアライズを見ていきます。蘇らせてはいけないデータの罠を、優しく見ていきましょう。

データを保存して、また読み戻すだけでしょ?何が危ないの?

復元のとき、データに紛れ込んだ『指示』でサーバ側のオブジェクトが勝手に組み立てられることがあるんだ。最悪はそのままRCEになる。

『データを保存して、あとで読み戻す』——プログラムでは毎日のように行う、ごく当たり前の処理です。それが、まさかサーバ乗っ取りにつながるなんて。今日のデシリアライズは、シリーズの中でも特に“地味なのに最凶”な部類です。見た目はただのデータの出し入れ。でも、その“読み戻し”の瞬間に罠が発動する。今日は冷凍弁当を電子レンジで温める話にたとえて、その不思議をほどいていきます。

まず結論

信頼できないデータをpickleunserializeObjectInputStreamなどでデシリアライズすると、攻撃者が用意したオブジェクトでRCEに至ることがあります。基本姿勢は「信頼できないデータをデシリアライズしない」「JSONなど安全な形式を使う」です。

この記事で分かること

  • シリアライズ/デシリアライズが何をしているのか
  • 復元時にRCEが成立する仕組み
  • 言語ごとの危険関数と、守る側の基本姿勢
難易度:すこし慣れてから 所要時間:10分 体験:復元処理を意識する おすすめ:#14の後

📖 はじめてのWebセキュリティ #15|デシリアライズ編
『データを蘇らせる処理』が、なぜRCEに化けるのかを、安全な題材で学びます。 シリーズ一覧を見る →

⚠️ 大事なお約束
この記事の確認は、CTF・公式ラボ・自分で作った検証環境だけで行ってください。実在のサービスに細工されたシリアライズデータを送る行為は、不正アクセスに該当する可能性があります。

シリアライズ/デシリアライズの基本

シリアライズとは、メモリ上のオブジェクトをファイルや通信で扱える「文字列(またはバイト列)」に変換することです。デシリアライズは逆に、その文字列をもとにオブジェクトを再度組み立て直す処理です。

多くの言語のシリアライズ形式は、ただの値だけでなく「どのクラスを使うか」「どんなメソッドを呼ぶか」も復元時に解釈します。ここに信頼できないデータを通すと、攻撃者が指定したクラス/メソッドが動いてしまうことがあります。

ここがデシリアライズの怖さの正体です。多くの言語の“荷ほどき”は、単に中身を取り出すだけではありません。「この荷物は、◯◯というクラスのオブジェクトとして組み立てて」「組み立てるときに、このメソッドを呼んで」という“指示書”まで、一緒に解釈してしまうんです。だから、信頼できない文字列を荷ほどきすると、攻撃者が書いた指示書どおりに、サーバが勝手に動き出してしまう。データのつもりが、実は“命令書”だった——というわけです。

まず言葉の整理から。『シリアライズ』は、プログラムの中で生きているオブジェクト(データのかたまり)を、保存や送信ができる“ひとつながりの文字列”に変換することです。引っ越しでいう“荷造り”ですね。逆に『デシリアライズ』は、その文字列をもとにオブジェクトを“組み立て直す”こと。つまり“荷ほどき”です。問題は、この荷ほどきの作業が、思った以上にいろいろなことを自動でやってしまう点にあります。

図解:正しい復元と攻撃データの復元

通常データのデシリアライズと、細工データを渡したときに復元中にRCEが発生する流れを比較した図
デシリアライズは『データ』だけでなく『クラスやメソッドの指定』まで復元してしまう。

同じデシリアライズ関数でも、正規データと細工データを渡したときの結末は大きく異なります。

電子レンジで冷凍弁当を温めると、家の他の家電まで自動で動き出してしまうたとえ図
温めただけで他の家電まで動く=復元だけで予期しない処理が走る、というデシリアライズの怖さ。
🍱 たとえるなら、冷凍弁当を温める

冷凍弁当(シリアライズデータ)を電子レンジで温めると、できあがるのは食べられる料理(オブジェクト)です。でも、もし弁当箱に「温め始めたら家中の電源を入れる仕掛け」が仕込まれていたら、温めただけで仕掛けが発動してしまいます。デシリアライズも同じで、復元の手順そのものに細工が仕込めるのが怖いところです。

ここで覚える用語:ガジェットチェーン
デシリアライズ攻撃で使われる、既存クラスの組み合わせのことです。攻撃者は単独で危険なクラスを使うのではなく、復元時に自動で呼ばれる無害そうなメソッドをつなぎ合わせて、最終的にコード実行に到達します。

『ガジェットチェーン』も、言葉は難しいですが発想はシンプルです。攻撃者は、いきなり“危険なクラス”を呼ぶわけではありません。プログラムにもともと備わっている、一見無害なメソッドたちを——Aを呼ぶとBが動き、Bが動くとCが呼ばれ…——とドミノのようにつなげて、最後にコード実行へたどり着くんです。一つひとつは無害な部品でも、つなぎ方しだいで凶器になる。だから「この部品は安全だから大丈夫」という単体の判断が通用しない、やっかいな攻撃なんです。

言語ごとの危険関数と発生場面

うれしくない話ですが、主要な言語にはたいてい“やってはいけない荷ほどき関数”が一つずつあります。Pythonならpickle、PHPならunserialize、JavaならreadObject……といった具合。名前は違っても、どれも「言語が独自の形式を、指示書ごと復元してしまう」という同じ性質を持っています。まずは自分の使う言語の“その関数”を知っておくことが、第一歩になります。

代表的な危険関数

Python・PHP・Java・.NET・Rubyごとのデシリアライズ危険関数を一覧で示したカード型インフォグラフィック
Python(pickle/yaml)・PHP(unserialize)・Java(readObject)・.NET(BinaryFormatter)・Ruby(Marshal.load)。外部データをそのまま渡さない。
  • Python: pickle.loads / yaml.load(safe_loadではないもの)
  • PHP: unserialize
  • Java: ObjectInputStream.readObject
  • .NET: BinaryFormatter / NetDataContractSerializer
  • Ruby: Marshal.load

これらの関数に「外部から届いたデータ」を直接渡している箇所が、デシリアライズ脆弱性の中心となります。発生場面はCookie・キャッシュ・キュー・セッション保存・APIなど多岐にわたります。

ここで見落としがちなのが、『外部から届いたデータ』の範囲です。フォームの入力だけだと思っていませんか?実は、Cookieの中身、キャッシュ、メッセージキュー、セッション保存——こういう“裏方のデータ”も、元をたどれば利用者から来ていることがあります。とくにCookieにオブジェクトをそのまま詰めて保存していると、利用者がそれを書き換えて送り返せてしまう。だから「どこまでが信頼できないデータか」を、いつもより少し広く見積もる必要があるんです。

今日の練習は、攻撃ペイロードを作ることではありません。もっと地味で、もっと実戦的——自分のコードを“grep(文字列検索)”して、危険な荷ほどき関数を探すだけです。見つかったら、「この関数に渡るデータは、どこから来ている?」と一つずつたどってみる。本物のサービスに細工データを送るのは、もちろん厳禁。宝探しの気分で、自分のコードを点検してみましょう。

CTFでやってみよう:復元処理を意識して読む

やってみよう / 演習環境限定

自分の検証環境のコードで、デシリアライズ関数を探そう

目的は実際に攻撃を作ることではなく、「どこで信頼境界を越えているか」を見ることです。

  1. CTFや自分のラボのコード/設定で、上記の危険関数を文字列検索する
  2. 該当箇所が「外部から届いたデータ(Cookie・リクエスト・キュー)」を扱っているか確認する
  3. 関数を json.loads や安全なパーサに置き換えられそうか考える
  4. 署名(HMAC)や暗号化が付いているか、Cookieの中身を実際に見て確認する
  5. 不要に複雑なオブジェクトを扱っていないか、データモデルを整理する
他人のサービスにpickleやunserialize payloadを送るのは絶対にやめてください。確認は自分のコード・自分のラボの範囲だけです。

守りの第一候補は『そもそも危ない荷ほどきをやめて、安全な形式に乗り換える』ことです。

JSONなら安全、ってよく聞くけど、何がそんなに違うの?

いい質問。JSONが扱えるのは、文字・数値・配列・真偽値みたいな“ただのデータ”だけなんだ。さっき言った「どのクラスで、どのメソッドを呼ぶ」という“指示書”を、そもそも持てない。だからJSONをいくら荷ほどきしても、データが出てくるだけで、勝手にプログラムは動かない。pickleやunserializeが危ないのは、まさにこの“指示書を運べてしまう”力があるから。安全な形式とは、つまり“データしか運べない形式”のことなんだよ。

守る側なら、「外部データはデシリアライズしない」を徹底

デシリアライズの守りは、「信頼できないデータを直接デシリアライズしない」「必要なら署名で改ざんを検出する」が基本です。

守るための基本チェック
  • 外部から受け取るデータの形式は、JSON・Protocol Buffersなど安全なフォーマットに統一する
  • どうしても言語固有のシリアライズを使う場合は、HMAC等で署名検証を必須にする
  • pickle・BinaryFormatter等の危険関数の使用箇所を棚卸しし、置き換え計画を持つ
  • Cookieやキャッシュにオブジェクトを格納するときは、内容を最小限・型を限定する
  • 復元できるクラスをホワイトリストに限定するライブラリ機能(filter, allow-list)を活用する
  • WAFや監査ログでデシリアライズの異常パターンを検知する仕組みを併設する

『復元』っていう、当たり前そうな処理が一番怖い場合があるんだね。

そう。デシリアライズは『見えにくいRCE』。だから関数の利用箇所を一覧化しておくのが、一番効く対策だよ。

ここまでをひと言で言うと、デシリアライズ対策は『信頼できないものは、蘇らせない』。荷ほどきは便利ですが、相手が誰かも分からない荷物を、無防備に開けてはいけません。安全な形式(JSON)に寄せる、どうしても言語固有の形式を使うなら署名で“すり替えられていないか”を確かめる。この2つが、守りの背骨になります。

まとめ:信頼できないデータは「蘇らせない」

今回のポイント
  • デシリアライズは復元時にクラス/メソッドが動く点が危険
  • 言語ごとに危険関数が決まっているので、まず棚卸しから
  • 守りの基本はJSON等の安全形式 + 署名検証 + ホワイトリスト
  • Cookieやキャッシュにオブジェクトを格納する設計は要見直し

今日の持ち帰りは『データだと思っていたものが、命令書かもしれない』です。デシリアライズは“見えにくいRCE”の代表で、コードを読んでも一見ふつうの処理に見えます。だからこそ、危険な関数の使用箇所を一覧にして、「ここに外部データは流れ込まない?」と定期的に点検する。地道ですが、これがいちばん効く守りです。

次回は、サーバが外部コマンドを呼び出す処理で起きるコマンドインジェクションを扱います。OSコマンドに混ざる悪意とRCEの経路を見ていきましょう。

次に読みたい記事

参考資料

記事URLをコピーしました