こんにちは!セキュリティの世界へようこそ。
新人のIT担当者の皆さんや、これからアプリ開発を頑張っていこうと考えている開発者の皆さん、日々の業務お疲れ様です。
セキュリティの勉強をしていると、SQLインジェクションやXSSといった有名な言葉はよく耳にすると思います。でも、今日お話しする「安全でないデシリアライズ(Insecure Deserialization)」という言葉は、少し難しそうに聞こえますよね。名前からして何やら怪しげで複雑な雰囲気がプンプンします。
大丈夫です!今回は、この少し取っつきにくい脆弱性を、身近な「宅配便」と「合鍵」に例えて、一歩ずつ優しく紐解いていきたいと思います。それでは、一緒に攻撃の仕組みと対策の世界を覗いてみましょう!
—
1. デシリアライズってそもそも何?(宅配便の荷物に例えてみよう)
アプリケーションを作っていると、プログラムの中で使っている「複雑なデータのかたまり(オブジェクト)」を、別の場所に送ったり、ファイルとして保存したりしたい場面がよくあります。
例えば、オンラインショッピングで「買い物カゴ」の中身をサーバーに送る時を想像してみてください。
カゴの中には「りんごが3個、みかんが2個」入っているという情報(データ構造)がありますよね。
- シリアライズ(Serialize):
プログラムが理解できる複雑な形をしたデータを、ネットワークで送りやすいように「ペチャンコに圧縮して、一つの文字列やバイト列(ダンボール箱)」に変換することです。
- デシリアライズ(Deserialize):
送られてきたダンボール箱(文字列)を開封して、再びプログラムが扱える元の「複雑なデータのかたまり(買い物カゴと中身)」に復元することです。
つまり、デシリアライズとは「届いた荷物を開封して中身を元の姿に戻す作業」のことなんですね。
—
2. 泥棒の侵入経路:なぜ「安全でない」と危ないのか?
ここで、今回の主役である「安全でないデシリアライズ」の登場です。
想像してみてください。あなたが自宅でネット通販の荷物待ちをしています。インターホンが鳴り、あなたは「Amazonからかな?」と何の疑いもせずドアを開けました。
しかし、そこに立っていたのは悪意ある泥棒です。泥棒は、見た目は本物のダンボール箱そっくりだけど、中身が「凶器」や「時限爆弾」に入れ替わった偽物の荷物をあなたに手渡しました。
あなたはそれを受け取り、家の中で「よいしょ」と箱を開封(デシリアライズ)してしまいました……。結果どうなるでしょうか? 箱から飛び出した危険な代物によって、あなたの家は乗っ取られてしまいますよね。
これが、アプリの世界で起きるリモートコード実行(RCE: Remote Code Execution)のメカニズムです。
1. 攻撃者は、サーバーがデシリアライズ処理を行う箇所(玄関)に向けて、悪意あるコードを仕込んだ「偽のシリアライズデータ(ダンボール箱)」を送りつけます。
2. アプリ側はそれを疑うことなく、いつものようにデシリアライズ(開封)してしまいます。
3. 開封された瞬間、データの中に潜んでいた「システムを乗っ取る命令(コード)」が勝手に実行され、サーバーが完全に攻撃者のものになってしまいます。
Java、PHP、Pythonなど、さまざまな言語でこの「信頼できないデータをそのままデシリアライズしてしまう」という実装ミスが原因で、多くのシステムがピンチに陥ってきました。
—
3. PHPでの危険なコード例と、その動きを見てみましょう
百聞は一見にしかず。PHPを例に、実際にどんなコードが危ないのかを見てみましょう。
以下のコードは、セキュリティ的に「やってはいけない」アンチパターンです。
<?php
// 【危険なサンプルコード】絶対に真似しないでください!
// ユーザーから送られてきたクッキー(Cookie)データを取得する想定
$user_cookie = $_COOKIE['user_session'];
// ⚠️ 警告: 信頼できないデータをそのままデシリアライズしているため致命的です!
// 攻撃者がこの中身を巧みに書き換えていると大変なことが起きます。
$decoded_data = unserialize($user_cookie);
// データの利用
echo "ようこそ、" . $decoded_data->username . "さん!";
?>
PHPには unserialize() という関数があります。これは非常に便利ですが、「渡された文字列がどんなものであろうと、言われるがままにオブジェクトに復元してしまう」というお人好しな性格を持っています。
もし攻撃者が、あらかじめ独自の危ないクラス(例えば、ファイル削除やシステムコマンドを実行するようなマジックメソッドを持つクラス)を定義し、それをシリアライズして細工した文字列をCookieに混ぜて送り込んできたらどうなるでしょう?
unserialize() が実行された瞬間に、その危ないクラスの処理が勝手に動き出し、サーバー上で任意のコマンド(例えば rm -rf / のような破壊的な操作など)が実行されてしまうのです。これがリモートコード実行の恐怖です。
—
4. どうやって防ぐの?(玄関のセキュリティを固めよう)
「じゃあ、デシリアライズ機能なんて怖くて使えないよ……」と思ったそこのあなた、安心してください!ちゃんと確実な防衛策が用意されています。
防犯に例えるなら、玄関のセキュリティをガチガチに固める作業が必要です。
対策1:そもそも信頼できないデータをデシリアライズしない
これが一番確実です。ユーザーから送られてきたデータ(CookieやPOSTデータなど)をそのまま unserialize() に突っ込むのは絶対にやめましょう。
代わりに、JSON形式などの「ただの文字列データ(構造化されていないデータ)」として受け取り、必要な値だけを安全に取り出して使うように設計変更するのが王道です。
対策2:型チェックとホワイトリストの導入(許可された人だけ通す)
どうしてもシリアライズされたデータを使う必要がある場合は、「ホワイトリスト方式」を採用します。
つまり、「このクラスのデータ以外は絶対に開封しません!」という厳しい番人を門番として置くのです。
PHPの unserialize() を使う場合は、以下のように allowed_classes というオプション(ホワイトリスト)を指定して、安全なクラス以外は拒否するように設定します。
<?php
// 【安全な実装のサンプルコード】
$user_cookie = $_COOKIE['user_session'];
// ホワイトリストを使って、特定の安全なクラス(例: UserProfileクラス)だけを許可する
$options = [
'allowed_classes' => ['UserProfile']
];
// 許可されていない危険なクラスが混ざっていた場合、強制的にエラー(または__PHP_Incomplete_Class)になります
$decoded_data = @unserialize($user_cookie, $options);
// ちゃんと期待したクラスのオブジェクトになっているか、念のため型チェックを行う
if ($decoded_data instanceof UserProfile) {
echo "安全なデータです。ようこそ、" . htmlspecialchars($decoded_data->username, ENT_QUOTES, 'UTF-8') . "さん!";
} else {
// 異常なデータが検知された場合の処理(ログ記録や遮断)
echo "不正なアクセスが検知されました。";
}
?>
このように、「受け入れるものを厳しく制限する」「中身を過信せず、プログラム側で型をしっかり確認する」という二段構えの防衛が、サイバー攻撃からシステムを守るカギになります。
—
5. まとめ:一歩ずつ、安全な開発者への道を歩もう
今回は「安全でないデシリアライズ」について、宅配便の荷物受け取りに例えて解説してきました。
- デシリアライズは「届いた箱を開封して中身を復元する作業」であること。
- 中身が偽物(悪意あるコード)にすり替わっていると、サーバーが乗っ取られる(RCE)危険があること。
- 信頼できない入力値をそのままデシリアライズせず、ホワイトリストや型チェックで厳しく門前払いすることが最高の防御策であること。
セキュリティの仕組みは最初は難しく感じるかもしれませんが、私たちの日常生活の防犯ルールと根本は同じです。「見知らぬ人からの怪しい荷物は開けない」「開けるとしても中身が安全かしっかり確認する」という意識を持つだけで、書くコードの安全性は劇的に向上します。
焦らず、一歩ずつ確実に対策の引き出しを増やしていきましょう。あなたの安全なコーディングの旅を、これからも応援しています!
コメント