XSSと「HttpOnlyの幻想」:セッションハイジャックを完封するための現場の流儀
現場でコードをレビューしていると、「HttpOnly属性を付けているからセッションハイジャックは大丈夫ですよね?」という質問をよく受ける。結論から言えば、それは「鍵をかけたから泥棒は一生入ってこない」と信じているのと同じだ。
HttpOnlyは確かに強力な防御策だが、XSS(クロスサイトスクリプティング)という怪物は、そんな単純な防壁をいとも簡単に飛び越えてくる。今回は、XSSの深淵と、セッションを守り抜くための「泥臭い防衛戦」について、現場の知見を詰め込んで解説する。
—
1. XSSの進化と「HttpOnly」の限界
なぜHttpOnlyだけでは不十分なのか
HttpOnly属性は、JavaScriptからのdocument.cookieアクセスを禁止する。これで「Cookieを盗み出す」という古典的な攻撃は防げるようになった。しかし、攻撃者は進化している。
- 反射型・格納型XSS: 確かにCookieの取得は難しくなる。だが、攻撃者は「Cookieを盗む」のではなく、「ブラウザ上で攻撃者の意図する操作を代行させる」方向にシフトした。セッションを盗まなくても、ログイン済みのユーザーのブラウザを使って、勝手に送金したり、パスワードを変更させたりする(CSRFのコンボ攻撃など)。
- DOMベースXSSの脅威: これが最も厄介だ。DOMベースXSSは、サーバーサイドのログに残らない。クライアントサイドのJavaScriptで生成される脆弱性であるため、WAF(Web Application Firewall)すら検知をすり抜けることが多い。
HttpOnlyがあろうがなかろうが、スクリプト実行権限を奪われた時点で、ブラウザという「踏み台」を攻撃者に開放したことに他ならない。
—
2. 攻撃者の視点:DOMベースXSSによるセッション「操作」
攻撃者が狙うのはCookieの盗難だけではない。彼らは「現在ログインしているユーザーのセッションを永続的に使って、ブラウザの中で好き勝手すること」を狙う。
例えば、以下のような脆弱なJavaScriptがあるとしよう。
// 危険なコード例:URLのパラメータをそのままDOMに書き込んでいる
const params = new URLSearchParams(window.location.search);
const username = params.get(‘name’);
document.getElementById(‘welcome-msg’).innerHTML = “Welcome, ” + username;
// 攻撃者が ?name= と送れば、即座にスクリプトが実行される
この時、攻撃者はCookieを盗む必要はない。このスクリプト実行権限を使って、fetch() APIで管理者権限のAPIを叩き、設定を書き換えるだけで十分なのだ。
—
3. 実践:セッションを確実に守る防御構成
「HttpOnlyはあくまで最後の砦」と考え、多層防御を敷くのがプロの仕事だ。
A. バックエンドでのCookie設定(PHPの例)
まずは基本中の基本、Cookieのフラグを厳格にする。
// php.ini またはコード内で設定
session_set_cookie_params([
‘lifetime’ => 0,
‘path’ => ‘/’,
‘domain’ => ‘yourdomain.com’,
‘secure’ => true, // HTTPS必須
‘httponly’ => true, // JSからのアクセスを遮断
‘samesite’ => ‘Strict’ // CSRF対策として最強の防御
]);
session_start();
B. フロントエンド:Content Security Policy (CSP) の導入
XSSが起きても「実行させない」のがCSPだ。NginxなどのWebサーバー設定、またはHTTPレスポンスヘッダーで制御する。
Nginx設定例:
信頼できないスクリプトの実行を強力に制限する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’; frame-ancestors ‘none’;”;
default-src 'self': 自サイト以外からのリソース読み込みを禁止。script-src 'self': インラインスクリプト()の実行を無効化する。これがXSS対策の要だ。
—
4. 結論:明日からエンジニアがやるべきこと
私が後輩によく言うのは、「ユーザー入力を一切信用するな」という当たり前の原則だ。
1. 出力エンコーディングの徹底: どんなデータもHTMLに埋め込む際は必ずエスケープする。フレームワーク(React, Vue, Laravel等)の自動エスケープ機能を盲信せず、dangerouslySetInnerHTMLのような「禁じ手」をコードレビューで徹底排除すること。
2. サニタイズの強化: どうしてもHTMLを許可する必要がある場合は、DOMPurifyのようなライブラリを使い、ホワイトリスト形式でタグを洗浄する。
3. WAFは「傘」に過ぎない: WAFは攻撃を「遅延」させるものだ。根本解決はソースコードにある。
HttpOnlyは素晴らしい発明だが、万能薬ではない。「セッションが盗まれること」を前提とした権限分離や、CSPによる実行環境のサンドボックス化こそが、現代のWeb開発において、エンジニアが守るべき最後の防壁だ。
このコードをそのまま実装して満足するのではなく、なぜこの設定が必要なのか、その裏側にある「攻撃者のロジック」を常に想像しながら開発に向き合ってほしい。それが、君を一流のエンジニアにする唯一の近道だ。
コメント