セッションハイジャックを許すな:Cookie属性とID再生成の「現場流」鉄則
現場でインシデント対応をしていると、いまだに「セッションIDが漏洩した」という悲鳴を聞く。攻撃者は高度なハッキングツールを使っているように見えるかもしれないが、実際はもっと泥臭い。ブラウザの隙間、通信の横取り、そして「ログイン前後でセッションIDを変えない」という初歩的な実装ミスを突いているに過ぎない。
今日は、教科書的な説明は抜きにして、なぜその設定が必要なのか、どう実装すれば攻撃者の付け入る隙をゼロにできるのかを、現場の知見を交えて徹底的に解説する。
—
1. なぜ「属性」が重要なのか:Cookieの防壁
Cookieは、ブラウザが勝手にサーバーへ送り届けてくれる「通行手形」だ。しかし、この手形はあまりに便利すぎて、悪意あるコード(XSSなど)から見れば「奪い放題の宝物」に見えている。
以下の3つの属性は、いわばブラウザに対する「厳格な命令」だ。
HttpOnly: JavaScriptからのCookieアクセスを禁止する。これがないと、document.cookieでセッションIDが盗まれる。Secure: HTTPS通信時のみCookieを送信する。これがないと、カフェのフリーWi-Fiでパケットを傍受(Sniffing)される。SameSite: クロスサイトリクエストを制限する。CSRF対策の要だ。
実装例:Nginxでの強制適用
アプリケーションコードだけでなく、インフラ層で「そもそもCookieを載せない」という強制力を持たせるのがプロの仕事だ。
# Nginxのレスポンスヘッダーで強制的に属性を付与する設定
# アプリ側で設定漏れがあっても、ここでカバーする防衛ライン
proxy_cookie_path / "/; HTTPOnly; Secure; SameSite=Lax";
—
2. セッション固定攻撃(Session Fixation)の盲点
最も見落とされがちなのが、ログイン処理だ。多くの開発者は「ログイン関数」を書く際、既存のセッションIDをそのまま使い回す。
攻撃のシナリオ:
1. 攻撃者が自作サイトでセッションID attacker_id を発行させる。
2. 攻撃者が被害者にその attacker_id をセットしたURLを踏ませる(あるいは細工したCookieを注入する)。
3. 被害者がその状態でログインすると、サーバーは「ログインしたから認証済みだ」と判断し、attacker_id に権限を付与する。
4. 攻撃者は attacker_id を使って、被害者になりすましてログイン状態を維持する。
これを防ぐ唯一の解は、「認証成功の瞬間にセッションIDを必ず再生成する」ことだ。
PHPでのセキュアな実装例
PHPの場合、session_regenerate_id(true) を呼ぶだけで古いセッションは破棄される。
<?php
// ログイン処理の核心部分
session_start();
// ユーザー名とパスワードの照合が終わった直後
if ($authenticated) {
// 【重要】古いIDを破棄し、新しいIDを生成する
// 引数trueは、古いセッションファイルを削除することを意味する
session_regenerate_id(true);
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = true;
}
?>
—
3. Python (Flask) でのモダンなセッション管理
Flaskのようなフレームワークを使う場合、フレームワークがよしなにしてくれる部分も多いが、設定を間違えれば無力だ。
from flask import Flask, session
app = Flask(__name__)
# セッション設定:実務では環境変数から読み込むこと
app.config.update(
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SECURE=True, # 本番環境では必ずTrueにする
SESSION_COOKIE_SAMESITE='Lax', # CSRF対策の基本
PERMANENT_SESSION_LIFETIME=3600 # 1時間でセッション切れにする
)
@app.route('/login', methods=['POST'])
def login():
# 認証成功後
session.clear() # 既存データをクリア
session['user_id'] = 123
return "Logged in"
—
セキュリティチーフからの「現場の助言」
コードを書くとき、常に「ブラウザが攻撃者の支配下にある」と仮定してほしい。
1. セッションの生存時間を短くする: どんなに強固な暗号でも、漏洩したIDが1週間有効であれば意味がない。php.ini やフレームワークの設定で、アイドルタイムアウト(例:30分)を必ず設定すること。
2. ログにセッションIDを出さない: 開発中のデバッグログに session_id() を出力するエンジニアが多すぎる。これはログファイルが漏洩した瞬間にシステムが陥落することを意味する。
3. ブラウザの仕様を過信しない: SameSite=Lax は強力だが、万能ではない。CSRFトークンなど、アプリケーション層での多重防御は不可欠だ。
セキュリティは、一度設定して終わりではない。技術の変化とともに攻撃手法も進化する。だが、今日紹介した「Cookie属性の厳格化」と「ログイン後のID再生成」は、10年前も今も変わらない「防衛の基盤」だ。まずは今動いているシステムのコードを見直し、HttpOnly が抜けていないか、ログイン処理でIDが切り替わっているかを確認することから始めてほしい。
手を動かすエンジニア諸君、足元を固めてから次のコードを書こう。それが最も近道だ。
コメント