【現場の教訓】javascript:スキームによるXSSは「URLフィルタリング」の甘さから生まれる
こんにちは。現場で泥をすすりながらインシデント対応をしてきた経験から言わせてもらうと、セキュリティ対策において「ブラックリスト方式」はほとんどの場合、敗北を意味する。特に、Webアプリケーションのプロフィール編集機能や掲示板などで、ユーザーにURLを入力させる実装をしているなら注意が必要だ。
今回取り上げるのは、javascript:スキームを用いたXSS(クロスサイトスクリプティング)だ。これ、教科書には載っているが、実務ではいまだに「え、これだけでいいの?」という甘い実装をして踏み抜くエンジニアが後を絶たない。
—
なぜ javascript: スキームは危険なのか?
攻撃者は、タグのhref属性や、のsrc属性に悪意のあるJavaScriptコードを仕込む。
攻撃のPoC(例):
ブラウザはjavascript:というプロトコルを見つけると、その後の文字列をJavaScriptコードとして実行する。もし管理画面や他ユーザーのブラウザ上でこのリンクがクリックされれば、セッションハイジャックや任意の操作が完遂される。
「httpかhttpsで始まればいいだろう」と安易に文字列検索だけで制御しようとするのが、現場で最も多い敗因だ。攻撃者は大文字・小文字を混ぜたり(JaVaScRiPt:)、タブコードや改行を挟み込んでフィルタを回避しようとしてくる。
—
守りの鉄則:URLの検証は「ホワイトリスト」一択
対策の基本は、「許可されたプロトコル以外はすべて拒否する」ことだ。正規表現で頑張ろうとするな。URLパーサーを通して、スキーム(プロトコル)を明示的にチェックするのが最も堅牢だ。
以下に、実務で使えるセキュアな実装例を示す。
1. PHPでの検証実装(サーバーサイド)
PHPのparse_url関数を使い、プロトコルを厳格にホワイトリストで照合する。
/
- URLが安全なスキームか検証する
- @param string $url 検証対象のURL
- @return bool
/
function is_safe_url(string $url): bool {
// URLをパース
$parts = parse_url(trim($url));
// スキームが取得できない、あるいは許可されたもの以外は拒否
$allowed_protocols = [‘http’, ‘https’];
if (!isset($parts[‘scheme’]) || !in_array(strtolower($parts[‘scheme’]), $allowed_protocols, true)) {
return false;
}
return true;
}
// 使用例
$user_input = $_POST[‘url’];
if (is_safe_url($user_input)) {
// 安全な場合のみDBに保存
save_url($user_input);
} else {
// エラー処理
throw new Exception(“許可されていないプロトコルです。”);
}
2. JavaScriptでの検証実装(クライアントサイド)
フロントエンドでも同様に、URLオブジェクトを利用してプロトコルを検証するのが現代的なベストプラクティスだ。
/
- DOMにリンクを挿入する前の検証ロジック
/
function sanitizeUrl(url) {
try {
const parsed = new URL(url);
// http: または https: のみ許可
if ([‘http:’, ‘https:’].includes(parsed.protocol)) {
return url;
}
} catch (e) {
// パースエラー(不正な形式)の場合は空文字を返す
return ”;
}
return ”; // javascript: 等はここで弾かれる
}
—
インフラ・防御レイヤーでの補強
コードの修正はもちろん必須だが、多層防御としてContent Security Policy (CSP)を必ず設定してほしい。HTTPレスポンスヘッダーで以下のように設定することで、万が一コードにバグがあっても、インラインスクリプトの実行をブラウザ側で禁止できる。
Nginxの設定例:
インラインスクリプトを禁止する(セキュリティを劇的に高める)
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;
チーフからのアドバイス
「たかがURLチェック」と侮ってはいけない。インシデントの9割は、こうした「どこかで見たような基本的な機能」の作り込みが甘い場所から発生する。
もし君が開発リーダーなら、チームにはこう伝えてくれ。
「フィルタリングを実装する際は、ブラックリスト(禁止リスト)を作るな。ホワイトリスト(許可リスト)のみを管理せよ」と。
これだけで、君たちのアプリケーションの堅牢性は一段階引き上がる。現場からは以上だ。また何かあればいつでも相談してくれ。
コメント