破壊の万能鍵:安全でないデシリアライズ(Insecure Deserialization)との戦い
現場でインシデント対応をしていると、「なぜこんな単純な入り口を放置したのか」と頭を抱えることがよくある。その筆頭が「安全でないデシリアライズ」だ。
多くのエンジニアは、シリアライズされたデータを「単なる通信用のフォーマット」と誤解している。だが、攻撃者にとってそれは、アプリケーションのメモリ空間を乗っ取り、OSレベルのコマンドを実行するための「魔法の杖」に他ならない。今日は、この脆弱性がなぜRCE(リモートコード実行)に直結するのか、そしてどうやって鉄壁の防御を築くのかを、泥臭い実務の視点から解説する。
—
1. なぜ「データ」が「コード」に化けるのか?
デシリアライズとは、メモリ上のオブジェクトを保存や転送のためにバイト列へ変換したもの(シリアライズ)を、元のオブジェクトへ復元するプロセスだ。
脆弱性は、「復元時に、どのクラスのインスタンスを生成するかを外部入力に委ねてしまう」ことで発生する。攻撃者は、アプリケーションが本来意図しないクラスを復元させることで、デストラクタ(破棄処理)やマジックメソッドが自動的に実行されるタイミングを狙い、悪意あるコードを仕込む。これが「ガジェットチェーン」と呼ばれる攻撃手法だ。
Pythonにおける危険な例(pickle)
Pythonの pickle モジュールは、最も危険な例の一つだ。以下は、攻撃者が送り込んできたデータがそのままシステムコマンドを実行してしまうPoCの簡略版である。
import pickle
import os
class Exploit:
def __reduce__(self):
# デシリアライズ時にos.systemが実行される
return (os.system, ('whoami',))
# 攻撃者が作成した悪意あるペイロード
payload = pickle.dumps(Exploit())
# 開発者が何も考えずに受け取ってロードすると...
pickle.loads(payload) # ここでコマンドが実行される
—
2. 現場で使える防御戦略:ホワイトリストへの回帰
「シリアライズデータを使わない」というのが究極の正解だが、レガシーなシステムではそうもいかない。その場合、「信頼できない入力は絶対にデシリアライズしない」という原則を徹底するしかない。
PHPでの安全な実装例
PHPの unserialize() を使う場合、必ず allowed_classes オプションを指定し、許可されたクラス以外は即座にエラーにする必要がある。
<?php
// 安全なデシリアライズ実装
$user_data = $_POST['data'];
// 許可リストを定義(これ以外はデシリアライズ時にstdClassに変換される)
$options = [
'allowed_classes' => ['UserSession', 'AppConfig']
];
$data = unserialize($user_data, $options);
if ($data === false) {
// ログに攻撃の痕跡を記録する
error_log("不正なデシリアライズの試行: " . $_SERVER['REMOTE_ADDR']);
die("不正なアクセスです。");
}
?>
Pythonで安全にデータを扱うなら(JSONへの移行)
Pythonであれば、pickle のようなコード実行を伴うフォーマットを捨て、json モジュールへ完全に移行すべきだ。
import json
# 信頼できない入力はシリアライズではなくJSONで受け取る
def safe_load(raw_data):
try:
# JSONはデータのみをパースするため、コード実行のリスクが極めて低い
return json.loads(raw_data)
except json.JSONDecodeError:
return None
—
3. インフラ層での「多層防御」
アプリケーション側の修正が間に合わない場合や、万が一の脆弱性混入に備えて、インフラ層で防壁を築く必要がある。
WAFによるシグネチャ検知(Nginx / ModSecurity)
攻撃者はシリアライズデータの中に特定のシグネチャを隠す。例えばPHPであれば O:8:"stdClass" のような文字列がペイロードに含まれることが多い。これをWAFでブロックする。
# ModSecurityのルール例(簡易版)
# シリアライズデータ特有の形式を検知してブロック
SecRule ARGS "O:[0-9]+:\"" \
"id:10001,phase:2,deny,log,msg:'Insecure Deserialization Attempt'"
AWS IAMでの権限最小化
万が一RCEが許されてしまったとしても、被害を最小限にするのがプロの仕事だ。アプリケーションが動くEC2やLambdaのIAMロールは、必要最低限の権限に絞り込んでいるか?
- NG:
s3:*権限を付与している。 - OK: 特定のバケットへの
s3:GetObjectのみ許可し、他のサービスへのアクセスを一切拒否する。
—
最後に:エンジニアとしてのマインドセット
「ライブラリがやってくれるから安全」という考えは、脆弱性の温床だ。デシリアライズに限らず、外部からの入力を扱う際は「これは爆弾かもしれない」と疑う癖をつけてほしい。
1. データフォーマットの選別: 基本は JSON や Protobuf を選ぶ。
2. 入力を信じない: デシリアライズする前には必ず署名検証(HMAC等)を行い、改ざんされていないことを確認する。
3. 継続的な監視: 異常なログを吐き出しているIPやユーザーエージェントを自動でBANする仕組みを構築する。
セキュリティは一度設定して終わりではない。常に攻撃者の視点を持ち、システムという名の砦を磨き続けること。それが我々エンジニアの責務だ。また現場で会おう。
コメント