OpenID Connectの「nonce」を軽視するな:リプレイ攻撃の現実と防御の実装
現場でインシデント対応をしていると、「なぜこんな初歩的な落とし穴にハマるのか」と頭を抱えたくなる瞬間がある。その筆頭が、OpenID Connect(OIDC)における nonce パラメータの検証不足だ。
「認証なんてライブラリがやってくれるから大丈夫」——そう思っているなら、今すぐ考えを改めたほうがいい。多くの開発者は「とりあえずログインできた」で思考を停止させ、その裏側にあるセキュリティの担保をブラックボックス化している。今回は、この nonce を巡る攻防と、明日から使える実装ルールを叩き込む。
—
1. なぜ nonce は「単なるおまじない」ではないのか?
OpenID Connectにおいて、認証リクエストとレスポンスを紐付けるために生成されるのが nonce(number used once)だ。
攻撃者は何を狙っているのか。それは、「有効なIDトークンを横取りし、それを別のセッションで再利用するリプレイ攻撃」だ。
もしあなたが nonce の検証をスキップ、あるいは実装をサボっていると、攻撃者は以下のようなフローでシステムを陥落させる。
1. トークンの傍受: 攻撃者は被害者のブラウザから流出したIDトークン(id_token)を、中間者攻撃やログの漏洩、あるいはブラウザの履歴から入手する。
2. リプレイ実行: 攻撃者は自身のブラウザで、入手したIDトークンをあなたのアプリケーションの認証エンドポイントに直接送信する。
3. セッションハイジャック: サーバー側で nonce の照合が行われていない場合、アプリケーションは「これは正当なユーザーのログインだ」と誤認し、被害者の権限でセッションを確立してしまう。
これは脆弱性というより、仕様を理解していないことによる「設計上の欠陥」だ。
—
2. 実践:セキュアな実装パターン
「理論はわかったが、どう書くのが正解か?」という声に応え、Node.js(Express + Passport)環境を想定したセキュアな実装例を示す。ライブラリ頼みではなく、検証ロジックがどこで動いているかを意識することが重要だ。
実装コード例(Node.js / Express)
// セッションにnonceを保存するミドルウェア
app.get('/login', (req, res) => {
// 予測困難なランダムなnonceを生成
const nonce = crypto.randomBytes(16).toString('hex');
// セッションに保存して後で検証できるようにする
req.session.nonce = nonce;
const authUrl = `https://idp.example.com/authorize?` +
`response_type=id_token&` +
`client_id=YOUR_CLIENT_ID&` +
`redirect_uri=YOUR_CALLBACK_URL&` +
`scope=openid&` +
`nonce=${nonce}`; // ここでnonceを付与
res.redirect(authUrl);
});
// コールバック処理での検証
app.post('/callback', (req, res) => {
const { id_token } = req.body;
const decodedToken = jwt.decode(id_token);
// 【重要】保存していたnonceと、トークン内のnonceを比較する
if (decodedToken.nonce !== req.session.nonce) {
console.error('不正なnonceです。リプレイ攻撃の可能性があります。');
return res.status(403).send('Invalid nonce');
}
// 成功時のセッション生成処理へ...
req.session.destroy(); // nonceは使い捨てにするため破棄する
});
実装のポイント
- 予測不可能性:
nonceには必ず暗号論的に安全な乱数生成器を使用すること。 - 使い捨て: 検証が終わったら、セッション内の
nonceは即座に破棄(req.session.destroy()やdelete)する。ここを忘れると、別の攻撃経路が生まれる。 - 検証の厳格化: 検証は「サーバーサイド」で必ず行うこと。ブラウザ側だけで完結させようなどとは思わないことだ。
—
3. インフラ・設定面での守り
アプリケーション層だけでなく、インフラのレイヤーでも防御を固める必要がある。
- CSP(Content Security Policy): IDトークンが漏洩するようなクロスサイトスクリプト(XSS)を防ぐため、
script-srcなどを適切に設定せよ。 - SameSite Cookie: セッションクッキーには必ず
SameSite=LaxまたはStrictを付与し、クロスサイトからのリクエストを制限する。 - WAFの活用: CloudflareやAWS WAF等のルールで、異常な頻度でのトークン送信を検知し、ブロックする設定を入れておくことも、多層防御として極めて有効だ。
—
4. セキュリティチーフからのアドバイス
「動けばいい」というコードは、セキュリティの観点では「爆弾」と同じだ。特に認証周りは、一度突破されると被害の範囲が測定不能になる。
1. ライブラリを過信しない: 多くのOIDCライブラリには nonce 検証オプションがあるが、それが有効になっているか、ドキュメントを読み込んで確認したか?
2. ペネトレーションテストの実施: 開発環境で、意図的に nonce を入れ替えたリクエストを投げてみてほしい。そこで弾かれないのであれば、本番環境にデプロイする資格はない。
セキュリティは「魔法」ではない。地道な仕様の理解と、それを裏切らないコードの積み重ねだ。次に誰かが「認証なんて簡単だよ」と言ってきたら、その背後にある nonce の検証ロジックを問い詰めてみてくれ。それが、君のチームを強くする第一歩になるはずだ。
コメント