【入門編】 Deserialization脆弱性(Java/PHP/Python)によるRCE – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

皆さん、こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。
「セキュリティ」って聞くと、なんだか難解な暗号や、映画に出てくるようなハッカーの画面を想像して身構えてしまいますよね。「自分にはまだ早いかも……」なんて思っていませんか?

でも、ご安心ください!今回は、Webアプリケーションの世界でひっそりと、しかし確実にシステムを崩壊させる危険を秘めた「デシリアライズ脆弱性」について、身近な「宅配便」と「合鍵」の例えを交えながら、一歩ずつ優しく紐解いていきたいと思います。

今日から使える安全なコードの書き方までしっかりお伝えしますので、ぜひ最後までリラックスして読んでいってくださいね。

—

1. 「シリアライズ」ってなに? —— 荷物の梱包と宅配便の例え

まずは、言葉の定義から柔らかく見ていきましょう。

JavaやPHP、Pythonといったプログラミング言語では、画面に入力された文字や、データベースから取り出したユーザー情報などを「オブジェクト(データのかたまり)」という便利な形でメモリ上に置いて処理しています。

しかし、このオブジェクトのままでは、ネットワークを通じて他のサーバーへ送ったり、ファイルとして保存したりすることができません。そこで使われるのが「シリアライズ(直列化)」です。

宅配便のダンボール箱に例えてみよう

例えば、あなたが精巧なロボット(オブジェクト)を友達に送りたいとします。
ロボットをそのままトラックに放り込むことはできませんよね? 一度、手足を折りたたんで、パーツごとに分解し、きれいにダンボール箱に詰めてガムテープで封をするはずです。これが「シリアライズ」です。

そして、友達の家に届いたダンボール箱を開け、中身を取り出して元のロボットに組み立て直す作業。これが「デシリアライズ(復元)」になります。

—

2. 恐ろしい「デシリアライズ脆弱性」の正体とは?

さて、ここからが本題です。
もし、あなたが送ったダンボール箱が、悪意ある第三者によって途中ですり替えられていたらどうなるでしょうか?

信頼できない相手から送られてきたダンボール箱(シリアライズされたデータ)の中身を確認もせず、言われるがままに家の中で開封し、組み立て直してしまったら……。
中からロボットではなく、危険な時限爆弾が飛び出してくるかもしれませんよね。

これが「デシリアライズ脆弱性」のメカニズムです。
プログラムが「これは信頼できるデータに違いない」と思い込み、受け取ったデータをデシリアライズ(復元)する際、そのデータの中に仕込まれていた「勝手にプログラムを実行する命令(悪意あるコード)」まで一緒に実行してしまうことで、サーバーが乗っ取られる(RCE:任意コード実行)という最悪の事態を引き起こします。

攻撃者は、私たちが何気なく使っている「クッキー(Cookie)」や「セッション情報」「アップロードファイル」などの隙を突き、この毒入りのダンボール箱を送り込んできたりするのです。

—

3. 被害を防ぐための「2つの鉄則」

じゃあ、どうやってこの脅威からシステムを守ればいいのでしょうか?
対策は大きく分けて2つあります。難しく考えず、順番に見ていきましょう!

鉄則その1:危険なデシリアライズ機能を安易に使わない(JSONへの移行)

もっとも確実な防御策は、プログラムのオブジェクトをそのまま文字列にするような、危険なシリアライズ機能(Javaの標準 ObjectInputStream や、PHPの unserialize() など)の利用をやめることです。

代わりに、データの中身(構造)だけをプレーンなテキストとしてやり取りする JSON などのフォーマットを使いましょう。JSONは「ただのデータ(値)」しか持たないので、たとえ悪意あるデータが混ざっていても、それを勝手にプログラムとして実行してしまう心配がありません。

鉄則その2:どうしてもの場合は「署名(デジタルスタンプ)」で守る

どうしてもオブジェクトの形式でデータをやり取りする必要がある場合は、届いた荷物が本物かどうかを確認する「封印シール(デジタル署名)」を必ず貼りましょう。

—

4. 実装コードで学ぶ!安全なデータ受け渡しのコツ

それでは、実務で使える具体的なコードを見ていきましょう。
今回は、脆弱なPHPのコードと、安全なJSONを使ったコードを比較してみます。

【危険な例】PHPの unserialize() をそのまま使う

以下のコードは、ブラウザから送られてきたクッキーのデータを、そのまま信じて復元してしまっている最悪の例です。

<?php
// 【危険】ユーザーからの入力をそのままアンシリアライズしている
if (isset($_COOKIE['user_session'])) {
    $raw_data = base64_decode($_COOKIE['user_session']);
    
    // 警告!信頼できないデータをそのまま復元すると、
    // オブジェクトのマジックメソッドが勝手に実行され、攻撃される危険があります!
    $user_obj = unserialize($raw_data); 
}
?>

【安全な例】JSONを使い、さらに安全にパースする

オブジェクトの復元をやめ、安全なJSONフォーマットに移行した例がこちらです。

<?php
// 【安全】安全なデータ交換フォーマットであるJSONを使用する
if (isset($_COOKIE['user_session'])) {
    $raw_data = base64_decode($_COOKIE['user_session']);
    
    // JSONとしてデコードする(第2引数をtrueにすると連想配列になる)
    // 万が一、悪意あるコード片が混入していても、文字列や数値として扱われるだけで実行されません
    $user_data = json_decode($raw_data, true);

    // データの整合性チェック
    if (json_last_error() === JSON_ERROR_NONE && isset($user_data['username'])) {
        // 安全に処理を続行
        $username = htmlspecialchars($user_data['username'], ENT_QUOTES, 'UTF-8');
        echo "ようこそ、" . $username . "さん!";
    } else {
        // 不正なデータの場合は処理を中断
        http_response_code(400);
        exit("不正なリクエストです。");
    }
}
?>

このように、データを扱う際は「中身が何であるかを厳しくチェック(バリデーション)」し、危険な自動実行機能を持つ仕組みを避けることが、エンジニアの第一歩であり最強の防御になります。

—

5. まとめ:一歩ずつ、安全な開発を楽しもう!

いかがでしたでしょうか?
デシリアライズ脆弱性と聞くと身構えてしまいますが、要するに「見知らぬ人から届いた怪しいダンボール箱を、中身も確認せずに家の中で開けてはいけない」という、現実世界のごく当たり前の防犯ルールと同じなんです。

  • 信頼できないデータをそのままプログラムのオブジェクトに復元しない。
  • 安全なデータフォーマット(JSONなど)へ移行する。
  • 入力値のバリデーションや署名検証を怠らない。

最初は覚えることが多くて大変かもしれませんが、一つひとつの仕組みを丁寧になぞっていけば、必ず安全なコードが書けるようになります。一緒に一歩ずつ、セキュアな開発スキルを磨いていきましょうね!

コメント

タイトルとURLをコピーしました