エンジニア諸君、現場のコードをレビューしていて「またこれか」と溜息をつきたくなる瞬間があるだろう。そう、「デバッグログの中に平文のパスワードやJWTが鎮座している」という惨劇だ。
セキュリティ診断の現場で、攻撃者が最初に狙うのがこの「ログ」だ。XSSの脅威については後述するが、まずはログの話をしよう。ログはシステムにとっての「日記」だが、攻撃者にとっては「宝の地図」だ。ここに機密情報(PII)が混入している時点で、そのシステムはすでに勝負に負けている。
—
1. ログが招く「地獄」:XSSと機密情報の密接な関係
なぜログの話からXSSを語るのか?理由はシンプルだ。「反射型XSS」の攻撃ペイロードがログに残り、それを監視ツール(DatadogやSplunk等)の管理画面で開いた管理者のブラウザ上でスクリプトが実行されるからだ。
攻撃者は、わざとエラーを引き起こす不正な入力を送りつけ、サーバー側に例外ログを書き込ませる。そのログには、攻撃者が仕込んだ が含まれている。これを、権限の高い管理者がダッシュボードで閲覧した瞬間にセッションハイジャックが成立する。
攻撃のPoC(概念実証)
1. 攻撃: https://example.com/api/search?q= を送信。
2. 蓄積: サーバー側のアプリケーションログに、このURLがそのまま出力される。
3. 発火: ログ監視ツールがこの文字列をHTMLとしてレンダリングする際、管理者のブラウザで攻撃コードが実行され、管理者のセッションCookieが外部へ送信される。
—
2. ログの機密情報を「物理的に遮断」する実装
「気をつける」という精神論は無意味だ。ライブラリの機能を使って、強制的にマスキングをかける。これが鉄則だ。
Python (structlog) を使った動的マスキング
Pythonのstructlogを使えば、ログ出力の直前に特定のキーを検出し、値をマスクする処理をパイプラインに組み込める。
import structlog
def mask_sensitive_data(logger, method_name, event_dict):
“””
特定のキーが含まれる場合、値を強制的にマスクするプロセッサ
“””
sensitive_keys = {“password”, “token”, “authorization”, “credit_card”}
for key in event_dict:
if key.lower() in sensitive_keys:
event_dict[key] = “”
return event_dict
structlog.configure(
processors=[
mask_sensitive_data, # 上記の関数をパイプラインに追加
structlog.processors.JSONRenderer() # JSON出力でログ解析ツールと親和性を確保
]
)
log = structlog.get_logger()
log.info(“user_login”, username=”admin”, password=”SecretPassword123″)
出力結果: {“event”: “user_login”, “username”: “admin”, “password”: ““}
PHP (Monolog) の場合
Monologを使っているなら、Processorを使って同様の処理を実装する。
use Monolog\Processor\ProcessorInterface;
class SensitiveDataProcessor implements ProcessorInterface {
public function __invoke(array $record): array {
$keysToMask = [‘password’, ‘token’, ‘api_key’];
foreach ($keysToMask as $key) {
if (isset($record[‘context’][$key])) {
$record[‘context’][$key] = ‘‘;
}
}
return $record;
}
}
// ロガーに登録
$logger->pushProcessor(new SensitiveDataProcessor());
—
3. XSSを根本から叩く:防御の「三種の神器」
ログを綺麗にしても、Webアプリケーション自体のXSS対策が甘ければ意味がない。以下の3点を徹底せよ。
① コンテキストに応じたエスケープ(基本中の基本)
「HTMLの中に埋め込むならhtmlspecialchars」というのは古い。JavaScriptの中に変数を埋め込むのか、CSSなのか、属性値なのか。コンテキストによってエスケープ手法は変わる。最近のモダンなフレームワーク(ReactやVue)は自動エスケープが強力だが、dangerouslySetInnerHTMLのような「魔の関数」を安易に使わないこと。
② CSP(Content Security Policy)で実行を封じる
もし脆弱性が見つかっても、実行させない。これが最強の防壁だ。HTTPレスポンスヘッダに以下を設定せよ。
Nginxの設定例: インラインスクリプトを禁止し、信頼されたソースのみ実行許可する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;”;
default-src 'self': 基本的に自ドメイン以外は読み込まない。script-src 'self': インラインスクリプトを許可せず、外部ソースも信頼できるCDNのみに絞る。
③ HttpOnly属性の付与
CookieにHttpOnlyを付与することは、XSSによるセッション盗難を物理的に不可能にする。「CookieはJavaScriptから触らせない」。これだけで、攻撃者はセッションを奪うという最大の目的を達成できなくなる。
—
最後に:プロのセキュリティとは「疑うこと」
いいか、コードを書くとき、常に「このログを攻撃者が読んだらどうなるか?」を想像しろ。そして、「この入力値がスクリプトとして解釈されたらどうなるか?」と自問自答しろ。
セキュリティは一度設定して終わりではない。OSのパッチと同じで、継続的な監視と「ログの健全性」の確認こそが、インシデントを防ぐ最短距離だ。
今日から君たちのプロジェクトのログを見直してくれ。そこに平文のパスワードが並んでいたら、それは君たちが攻撃者に「どうぞ、ここから侵入してください」と招待状を送っているのと同じだぞ。健闘を祈る。
コメント