【実務・中級編】 認可サーバーのメタデータエンドポイントの悪用 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

認可サーバーの「地図」を公開するな: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 も同様に保護対象だ。ここが書き換えられたら、認証処理が乗っ取られる。

セキュリティとは、魔法の杖を振ることではなく、こうした「当たり前の情報の漏洩」を一つずつ潰していく泥臭い作業の積み重ねだ。

君たちの書いたコードが、いつか「完璧な要塞」として機能することを期待している。何かあればいつでも相談してくれ。健闘を祈る。

コメント

タイトルとURLをコピーしました