セッションIDの「予測」は致命傷:CSPRNGを使わない開発者が犯す最大の過ち
いいか、現場で一番怖いのは「なんとなく動いているコード」だ。
特に、Webアプリケーションの心臓部である「セッション管理」。ここを甘く見ているエンジニアは、たとえ他のセキュリティ対策が完璧でも、玄関の鍵を爪楊枝で閉めているようなものだ。
今日は、セッションIDの生成において、なぜ「標準的な乱数関数」を使ってはいけないのか、そしてなぜ「暗号論的に安全な擬似乱数生成器(CSPRNG)」が唯一の正解なのかを、泥臭いインシデントの視点から解説する。
—
1. なぜ `rand()` や `Math.random()` ではダメなのか
多くの初心者が犯すミスは、`rand()` や `mt_rand()`(PHP)、あるいは `Math.random()`(JS)をセッションID生成に使ってしまうことだ。
これらの関数は「高速に乱数を作る」ことには長けているが、「予測不能であること」を保証していない。
攻撃者の視点:IDを「推測」するということ
攻撃者は、過去に発行された数千〜数万個のセッションIDを収集する。もし、内部の乱数生成アルゴリズムが線形合同法などの単純な仕組みであれば、現在の状態(シード値)を数式で逆算できてしまう。
一度「次のID」が推測できれば、あとは簡単だ。ログイン中のユーザーのセッションIDを特定し、そのユーザーになりすます(セッションハイジャック)。WAFもIDSも、この「正規のログインプロセスを通過したなりすまし」を止めることはできない。
—
2. 【言語別】コピペで使えるセキュアな実装
「とにかく安全なIDが欲しい」というとき、OSや言語が提供するCSPRNG(Cryptographically Secure Pseudo-Random Number Generator)を利用するのが鉄則だ。
PHPの場合:`random_bytes()`
PHP 7以降なら、迷わずこれを使え。`mt_rand()` などは論外だ。
/
function generate_secure_session_id() {
// 32バイト(256ビット)の乱数を生成し、URLセーフなBase64エンコードを施す
// これにより十分なエントロピーが確保される
return rtrim(strtr(base64_encode(random_bytes(32)), ‘+/’, ‘-_’), ‘=’);
}
// セッション開始時に適用
session_id(generate_secure_session_id());
session_start();
Pythonの場合:`secrets` モジュール
Python 3.6以降で導入された `secrets` を使え。
import secrets
32バイトの乱数を生成し、URLセーフなトークンとして出力
セッションIDとしてそのままDBに保存して利用可能
secure_token = secrets.token_urlsafe(32)
print(f”セッションID: {secure_token}”)
Node.jsの場合:`crypto` モジュール
`Math.random()` はブラウザでもサーバーサイドでも絶対にNGだ。
const crypto = require(‘crypto’);
// 32バイトの乱数を生成
const sessionId = crypto.randomBytes(32).toString(‘hex’);
console.log(`セッションID: ${sessionId}`);
—
3. 実践:セッションの「守り方」の極意
コードで安全なIDを作っただけでは油断するな。以下の設定をインフラやフレームワークの層で徹底してくれ。
Nginx/App Serverでの設定ポイント
セッションIDがどれほど強固でも、盗聴されたら終わりだ。
1. Secure属性: `Set-Cookie` には必ず `Secure` を付ける。HTTPS環境下でしか送信されないようにする。
2. HttpOnly属性: JavaScriptからのアクセスを遮断し、XSSによるID抜き取りを防ぐ。
3. SameSite属性: `Lax` または `Strict` を設定し、CSRFによる攻撃を緩和する。
Nginxでセッションクッキーにフラグを立てる例(`proxy_cookie_path`利用):
セッションクッキーにSecure; HttpOnly; SameSite=Lax を強制的に付与する設定
proxy_cookie_path / “/; HTTPOnly; Secure; SameSite=Lax”;
—
最後に:プロのエンジニアへのアドバイス
セキュリティの知識は「知っている」ことと「実装できている」ことの間に、深い溝がある。
今回紹介したCSPRNGの使用は、あくまで「IDの強度」の話だ。しかし、これすら守れないシステムは、他のセキュリティホールもザルである可能性が高い。
- ライブラリを信頼するな: 自作の乱数生成ロジックは絶対作るな。暗号学者の成果を甘く見るな。
- 常に更新せよ: フレームワーク(Laravel, Django, Express等)のデフォルトのセッション管理は非常に優秀だ。特別な理由がない限り、車輪の再発明はせず、フレームワークの標準機能を「正しく設定して」使うこと。
君たちが書くコードの一行一行が、誰かの大切な情報を守っている。その責任感を持って、今日のコードをリファクタリングしてくれ。
もし実装で詰まったら、いつでも相談してくれ。現場からは以上だ。

コメント