[解説] Auth.js (next-auth) v5における認証スキップの脆弱性(GHSA-8fpg-xm3f-6cx3)
Auth.js (next-auth) v5の「Fail Open」脆弱性とは?
フロントエンド開発、特にNext.jsなどのフレームワークで認証機能を手軽に実装できるAuth.js(旧next-auth)は、多くのプロジェクトで利用されています。今回解説するGHSA-8fpg-xm3f-6cx3は、Auth.js v5の特定の条件下で、認証チェックが意図せず通過してしまう「Fail Open」(安全ではない側に開いてしまう)という深刻な脆弱性です。
この脆弱性は、Auth.jsの設定にサーバーサイドでエラーが発生した際に顕在化します。通常、認証されていないリクエストに対しては`auth`オブジェクトが`null`として扱われますが、設定エラー時には`auth`オブジェクトがエラー内容を含むオブジェクトで初期化されてしまいます。JavaScriptでは空でないオブジェクトはtruthyな値として評価されるため、`!!auth`のような単純な存在チェックが常に`true`となり、未認証のユーザーにもアクセスが許可されてしまうのです。
具体的に何が問題なのか?
Auth.jsの公式ドキュメントでは、ミドルウェアやRoute Handlerなどでリソースを保護するために、`auth`オブジェクトの存在をチェックするパターンが示されています。例えば、以下のようなコードです。
```ts // middleware.ts — 影響を受けるパターン export default auth((req) => { const { nextUrl, auth } = req const isLoggedIn = !!auth // <-- 設定エラー時、常にtrueとなる // ... }) ```
この`isLoggedIn`が認証状態の判断に使われている場合、Auth.jsの設定エラー(例:Keycloakプロバイダーの`issuer`や`authorization`エンドポイントの未設定、あるいは`AUTH_SECRET`環境変数の未設定など)が発生すると、`auth`オブジェクトは以下のようなエラーオブジェクトで埋められます。
```json { "message": "There was a problem with the server configuration. Check the server logs for more information." } ```
このエラーオブジェクトはtruthyな値であるため、`!!auth`は`true`と評価され、結果として認証システムが壊れていても、すべてのリクエストが認証済みとして扱われてしまいます。アプリケーションはアクセスを拒否する代わりに、すべての人にアクセスを許可してしまう「Fail Open」状態に陥るのです。これはCWE-636(Not Failing Securely)およびCWE-285(Improper Authorization)に該当します。
どのようなケースで影響を受けるか?
この脆弱性は、**Auth.jsの設定が有効な間は影響がありません**。問題となるのは、デプロイ中に環境変数が変更または削除されたり、設定ミスによってアプリケーションがデプロイされたりした際など、これまで正常に動作していたデプロイが誤った設定になった場合です。このとき、存在ベースの認証チェックはサイレントに機能しなくなり、全ての訪問者が認証済みとして扱われる可能性があります。その結果、機密情報への不正アクセスなど、甚大な被害につながる可能性があります。
対応策:パッチ適用と回避策
この問題は、Auth.jsの最新バージョン`next-auth@beta`で修正されています。修正されたバージョンでは、サーバー設定エラーが発生しても`auth`オブジェクトがtruthyな値として扱われないようになり、存在チェックは「Fail Closed」(安全な側に閉じる)するように変更されました。速やかにアップグレードすることが強く推奨されます。
```sh npm i next-auth@beta # または yarn add next-auth@beta # または pnpm add next-auth@beta ```
もしすぐにアップグレードできない場合は、以下の回避策を適用してください。`auth`オブジェクトの存在だけでなく、具体的なユーザー/セッションプロパティ(例: `user`)の存在を確認するように変更します。これにより、設定エラーオブジェクトが認証済みのセッションとして扱われることを防げます。
```ts // middleware.ts export default auth((req) => { // `auth.user`は実際のセッションがある場合にのみ存在するため、設定エラーオブジェクトの影響を受けない const isLoggedIn = !!req.auth?.user // ... }) ```
また、Defense in Depth(多層防御)の観点から、デプロイパイプラインでAuth.jsの設定エラー(例: `[auth][error]`ログ行)を検知し、それをヘルスチェックの失敗として扱うなど、エラー発生時にはデプロイを中止する仕組みを導入することをお勧めします。これにより、壊れた設定が本番環境に到達するのを防ぐことができます。
最後に、既存のセッションは認証を示すものに過ぎず、認可(Authorization)とは異なります。認可のためには、セッションの存在だけに頼らず、明示的なロールや権限のチェックを行うようにしてください。詳細は公式の<a href="https://authjs.dev/guides/role-based-access-control" target="_blank" rel="noopener noreferrer">ロールベースアクセス制御ガイド</a>をご参照ください。
まとめ
Auth.js v5におけるこの「Fail Open」脆弱性は、設定エラーという比較的遭遇しやすい状況で発生し、認証機構を完全に無効化してしまう可能性があるため、Criticalな深刻度とされています。フロントエンドエンジニアの皆様は、ご自身のプロジェクトがAuth.js v5を利用している場合、速やかに最新のベータ版へのアップグレードを検討し、それが難しい場合は回避策を適用して、セキュリティを確保してください。
この脆弱性は、@marc-zollingkoffer-syzygy氏によって報告されました。責任ある開示に感謝いたします。
参考情報
<ul> <li><a href="https://authjs.dev/getting-started/session-management/protecting" target="_blank" rel="noopener noreferrer">Protecting resources / session management</a></li> <li><a href="https://authjs.dev/guides/role-based-access-control" target="_blank" rel="noopener noreferrer">Role-based access control (RBAC)</a></li> <li><a href="https://authjs.dev/reference/core/errors" target="_blank" rel="noopener noreferrer">Auth.js error reference</a></li> <li><a href="https://authjs.dev/security" target="_blank" rel="noopener noreferrer">Auth.js security policy</a></li> </ul>