認可サーバーの「地図」を公開するな:OIDCメタデータエンドポイントが招く悪夢と防御策
こんにちは。セキュリティチームのチーフエンジニアだ。
今日は、多くの開発者が「公開して当然」と思っている、あるいは単に設定を忘れて放置している「あるエンドポイント」について話そう。OpenID Connect (OIDC) の /.well-known/openid-configuration だ。
多くのエンジニアは「これは仕様だから」と高をくくっているが、攻撃者にとって、このエンドポイントは「ターゲットシステムの全貌を晒す地図」に他ならない。今回は、このメタデータがどう悪用され、どう防ぐべきかを、現場の視点から叩き込む。
—
1. なぜ「設定情報」が脅威になるのか?
OIDCのメタデータエンドポイントは、クライアントが認可サーバーと通信するために必要な情報を一括で提供する便利な機能だ。しかし、ここには以下のような「攻撃者が喉から手が出るほど欲しい情報」が詰まっている。
- エンドポイントの場所: 認可、トークン、ユーザー情報、公開鍵(JWKS)の各URL。
- サポートされている機能: 使用可能な認可フロー(
code,id_token等)、スコープ、クライアント認証メソッド。 - 脆弱性のヒント: サポートされている署名アルゴリズム(
RS256,HS256等)。
攻撃者は、このエンドポイントを叩くことで、手動で探りを入れる手間を省き、「どの脆弱性を突けば認証をバイパスできるか」を自動的に判別するスクリプトを走らせる。例えば、弱点のある署名アルゴリズムを強制させる攻撃や、公開されたJWKSエンドポイントからキーを不正取得する攻撃の準備が、一瞬で完了してしまうんだ。
—
2. 攻撃者が行う「情報収集」の手口(PoC)
例えば、攻撃者がPythonを使ってターゲットのメタデータを引き抜くのは、数行のコードで事足りる。
import requests
# ターゲットのドメイン
target = "https://auth.example.com"
well_known_url = f"{target}/.well-known/openid-configuration"
try:
response = requests.get(well_known_url, timeout=5)
# ここで返ってきたJSONの中身を解析し、
# 脆弱なアルゴリズムや、隠しエンドポイントを特定する
print(response.json())
except Exception as e:
print(f"Failed to fetch metadata: {e}")
この結果を見て、攻撃者は「お、ここは none アルゴリズムを許容している可能性があるな」とか「古いトークンエンドポイントが生きてるな」と、攻撃のシナリオを書き換える。君たちが必死に隠蔽したつもりでも、このエンドポイントが「正解」を教えてしまっているわけだ。
—
3. 完全防御へのアプローチ:アクセス制限と情報の最小化
このエンドポイントを「全人類に公開する必要があるか?」を自問してほしい。B2Bの社内システムであれば、IP制限をかけるのが鉄則だ。
Nginxでのアクセス制限設定
不特定多数からのアクセスを防ぐため、特定IPや内部ネットワーク以外からのアクセスをブロックする設定だ。
location /.well-known/openid-configuration {
# 信頼できるIPアドレスのみ許可
allow 192.168.1.0/24;
# 開発用VPNのIPなど
allow 10.0.0.5;
# それ以外は403で拒否
deny all;
# 必要に応じてログレベルを上げ、不審なアクセスを監視
access_log /var/log/nginx/auth_access.log combined;
}
アプリケーション層での制御
もしインフラ側で制御できない場合は、アプリケーション側でリクエスト元をチェックする。Node.js (Express) の例を見てみよう。
const express = require('express');
const app = express();
// メタデータエンドポイントの保護
app.get('/.well-known/openid-configuration', (req, res) => {
const allowedIps = ['123.45.67.89']; // 許可されたクライアントのIP
const clientIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
if (!allowedIps.includes(clientIp)) {
return res.status(403).send('Forbidden: Access Restricted.');
}
// 許可された場合のみJSONを返す
res.json({
issuer: "https://auth.example.com",
authorization_endpoint: "https://auth.example.com/authorize",
// ... 他の構成情報
});
});
—
4. チーフエンジニアからの提言:運用の要諦
最後に、現場で生き残るための「鉄則」を伝授する。
1. 「公開が前提」という思い込みを捨てる: 外部公開が必要な場合でも、情報を削ぎ落とせ。不要な拡張機能や、マイナーなフローはメタデータから除外する設定(またはカスタム実装)を検討すること。
2. 監視を疎かにしない: このエンドポイントへのアクセスログは、攻撃の「偵察フェーズ」の兆候だ。突如として未知のIPからこのエンドポイントへのアクセスが増えたら、それは攻撃者があなたの城の図面を確認している合図だ。即座にIPブロッキングの準備をしろ。
3. JWKSエンドポイントの保護: 署名検証用の公開鍵を置く jwks_uri も同様に保護対象だ。ここが書き換えられたら、認証処理が乗っ取られる。
セキュリティとは、魔法の杖を振ることではなく、こうした「当たり前の情報の漏洩」を一つずつ潰していく泥臭い作業の積み重ねだ。
君たちの書いたコードが、いつか「完璧な要塞」として機能することを期待している。何かあればいつでも相談してくれ。健闘を祈る。
コメント