OAuth 2.0「スコープの過剰付与」という名の時限爆弾:なぜあなたのアプリは「鍵束ごと」渡してしまうのか
現場でコードレビューをしていると、必ずと言っていいほど目にする「思考停止」がある。それはOAuth 2.0のスコープ設定だ。
「とりあえず動けばいいから、readもwriteも全部入りのスコープにしておこう」
もし君がそう思っているなら、今すぐその手を止めてほしい。それは、家の玄関の鍵を渡す代わりに、金庫の鍵も車の鍵も、何なら隣の家の鍵まで束ねて相手に渡しているようなものだ。これが「スコープの過剰付与(Over-scoping)」であり、権限昇格攻撃の格好の標的となる。
1. なぜ「全権限」が危険なのか?:攻撃者の視点
攻撃者は、アプリケーションの脆弱性(例えば、SSRFやオープンリダイレクト)を突いて、ユーザーのアクセストークンを盗み出そうとする。もし君のアプリがuser.profileだけで済むところにuser.allやadmin.writeを要求していたらどうなるか。
1. トークン窃取: 攻撃者は奪ったトークンを使って、ユーザーになりすましてAPIを叩く。
2. 権限昇格: 本来ユーザー自身ですら操作できないはずの、他のユーザーの個人情報閲覧や、管理機能へのアクセスが可能になる。
3. 被害拡大: サービス全体が乗っ取られる前に、攻撃者は持ち出せるデータを根こそぎ抜き取る。
これは単なる「設定ミス」ではなく、「設計上の欠陥」だ。
2. 実践的な防御策:必要最小限(Principle of Least Privilege)の鉄則
OAuthの設計において、スコープは「最小限の機能」に分割するのが大原則だ。以下の指針を守れ。
- スコープを細分化する:
scope=read_writeではなく、scope=read_profile,write_settingsのように分ける。 - 動的な要求: ユーザーが「写真の投稿」機能を使うその瞬間に、初めてそのスコープを要求するようにフローを組む。
- 拒否を許容する: ユーザーが一部のスコープを許可しなくても、アプリが最低限の機能で動き続けるように設計する。
3. コピペで学ぶ:セキュアな実装例(Node.js/Express)
OAuthクライアントとして、スコープを適切に管理する実装の例だ。ライブラリ(passport-oauth2等)を使う場合でも、パラメータの渡し方が重要になる。
/
- セキュアなOAuth2認証リクエストの構築
- 必要な権限だけを抽出し、スコープを最小限に抑える
/
const getOAuthUrl = (userIntent) => {
const baseUrl = “https://provider.example.com/oauth/authorize”;
// ユーザーの意図(intent)に基づいてスコープを動的に切り替える
const scopes = [‘openid’, ‘profile’]; // 基本スコープ
if (userIntent === ‘UPLOAD_PHOTO’) {
scopes.push(‘photo.write’); // 必要な時だけ書き込み権限を追加
}
const params = new URLSearchParams({
client_id: process.env.CLIENT_ID,
redirect_uri: process.env.REDIRECT_URI,
response_type: ‘code’,
// スコープを配列からスペース区切りの文字列に変換
scope: scopes.join(‘ ‘),
state: generateSecureRandomState() // CSRF対策用のstate値を必ず生成
});
return ${baseUrl}?${params.toString()};
};
4. インフラ・ガードレール:WAFによる保護
コード側の修正だけでなく、インフラ側でも「不正なスコープ要求」を弾く準備をしておこう。例えば、CloudFrontやNginxレベルで、認可コードリクエスト時のパラメータを監視するのも手だ。
Nginxでのリクエスト制限(概念的な設定):
もし特定のパスに対して、異常に広いスコープを含むリクエストが頻発しているなら、ログ出力とレートリミットをかける。
特定の認可エンドポイントへの過剰なスコープリクエストを検知する例
location /oauth/authorize {
# 認可リクエストに特定の危険なキーワードが含まれていないかチェック
if ($query_string ~ “scope=.admin.”) {
# 本来不要なadmin権限を要求する怪しいリクエストをログに記録
access_log /var/log/nginx/security_violation.log security_json;
# 必要に応じてブロック、またはレート制限を適用
limit_req zone=auth_limit burst=5 nodelay;
}
proxy_pass http://auth_server;
}
最後に:セキュリティは「面倒くささ」との闘い
君たちが書くコードの一行一行が、ユーザーの資産とプライバシーを守る盾になる。スコープを分けるのは確かに面倒だ。管理するエンドポイントが増えるし、バリデーションも複雑になる。
だが、一度インシデントが起これば、その「面倒」の何百倍ものコストを払うことになる。インシデントハンドリングの現場で、真っ青な顔をした開発者を見たくはないだろう?
「動く」ことはゴールじゃない。「安全に動き続ける」ことこそが、プロフェッショナルの仕事だ。今日から、君のアプリのスコープを見直してくれ。それが、世界最高峰のエンジニアへの第一歩だ。
コメント