なぜ「アクセストークンがあればUserInfoが叩ける」という油断が、致命傷になるのか
現場でコードレビューをしていると、必ずと言っていいほど目にする「やらかし」がある。それは、「OIDCのアクセストークンさえあれば、UserInfoエンドポイントは誰でもアクセスして良い」という誤った前提だ。
多くの開発者は、OAuth 2.0やOIDCのフローを実装する際、認可サーバーから発行されたアクセストークンを「万能の通行証」だと勘違いしている。だが、セキュリティの最前線にいる我々から見れば、それは「鍵束を渡しただけで、どの部屋に入っていいかを確認していない」のと同じくらい危険な状態なんだ。
今日は、UserInfoエンドポイントにおける「スコープ検証の欠落」という盲点について、実戦的な話をしよう。
—
1. 攻撃者が狙う「スコープの横展開」という盲点
UserInfoエンドポイントは、ユーザーのプロファイル情報を返すAPIだ。もし、あるサードパーティアプリが「email」スコープしか要求していないのに、アクセストークンを悪用して、本来権限のない「address」や「phone_number」まで取得できてしまったらどうなるか?
これが、いわゆるスコープの不適切な検証(Insufficient Scope Validation)による情報漏洩だ。
攻撃者のPoCシナリオ
1. 侵害の発生: ユーザーAが、悪意ある「画像加工アプリ(仮)」にログインする。このアプリは本来不要な profile や address スコープまで要求し、ユーザーが誤って承認する。
2. トークンの悪用: 攻撃者はこのアクセストークンを使用し、本来の意図を超えて、認可サーバーの /userinfo エンドポイントに対し、意図しない属性情報を要求する。
3. 脆弱な実装: サーバー側が「アクセストークンが有効かどうか」しかチェックしておらず、「そのトークンに該当属性を返す権限(スコープ)があるか」を照合していない場合、攻撃者はユーザーのプライバシー情報を丸裸にできる。
—
2. セキュアな実装:アクセストークンの「中身」を疑え
UserInfoエンドポイントを実装する際、最も重要なのは「認可サーバーへの問い合わせ」だけで満足しないことだ。受け取ったアクセストークンが、どのスコープを保持しているかを必ず検証しなければならない。
以下に、Node.js (Express) を想定した実戦的な実装例を示す。
実装例:スコープ検証を組み込んだUserInfoエンドポイント
/
- セキュアなUserInfoエンドポイントの例
- アクセストークンを検証し、要求されたスコープと照合する
/
const verifyAndGetUserInfo = async (req, res) => {
// 1. Authorizationヘッダーからトークンを取得
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(‘ ‘)[1];
if (!token) return res.status(401).json({ error: ‘トークンがありません’ });
try {
// 2. トークンイントロスペクション(認可サーバーでの検証)
const tokenData = await introspectToken(token);
if (!tokenData.active) {
return res.status(401).json({ error: ‘トークンが無効です’ });
}
// 3. 【最重要】スコープの検証
// このトークンが ‘profile’ スコープを持っているか確認する
const allowedScopes = tokenData.scope.split(‘ ‘);
if (!allowedScopes.includes(‘profile’)) {
return res.status(403).json({ error: ‘このエンドポイントへアクセスする権限がありません’ });
}
// 4. 検証成功後、安全にデータを返す
const userData = await getUserProfileFromDB(tokenData.sub);
res.json(userData);
} catch (err) {
res.status(500).json({ error: ‘内部サーバーエラー’ });
}
};
—
3. なぜこれで防げるのか?(現場の教訓)
この実装の肝は、「認可サーバーが発行したトークンのスコープリスト(scope クレーム)」を、API側で厳格に比較している点だ。
もし攻撃者が「email しか持っていないトークン」を提示しても、ステップ3の includes('profile') で弾かれる。開発現場では、「動けばいい」という理由でスコープチェックを省くコードが散見されるが、認証と認可は別物だ。認証が通っても、認可(権限)の範囲内であるかを確認しなければ、システムはザル同然になる。
さらに堅牢にするためのインフラTips
- WAFの活用: もしAPIが特定のスコープを要求するエンドポイントに固定されているなら、CloudFront WAFやAWS WAFのカスタムルールで、特定のパスへのアクセス時に特定のヘッダーやトークンの形式を監視するのも一つの手だ。
- OAuth 2.0 Token Introspection (RFC 7662): 可能な限り、リソースサーバーは都度イントロスペクションエンドポイントを叩くか、JWTの検証時に
scopeクレームを必ずデコードして検証するルールを徹底してほしい。
—
最後に:セキュリティは「性悪説」から始まる
エンジニア諸君、アプリケーションのコードを書くとき、常に「このアクセストークンは、攻撃者が偽造したり、別の目的で取得されたものかもしれない」と考えてほしい。
UserInfoエンドポイントのスコープ検証は、単なる「仕様」ではなく、ユーザーの信頼を守るための「最後の一線」だ。今日紹介したコードは、今すぐ君のプロジェクトのミドルウェアに組み込めるはずだ。
「面倒くさい」が「情報漏洩」に繋がる。そのことを忘れないでほしい。何か不明点があれば、またいつでも相談してくれ。我々の仕事は、コードを書くことではなく、ユーザーの安全を設計することなのだから。
コメント