NocoBaseの認証済みRCE脆弱性(GHSA-ghvf-qf6h-g8x5)を深掘り:フロントエンド開発者が知るべきこと
はじめに:なぜフロントエンドエンジニアがバックエンドのRCEを知るべきか?
フロントエンド開発者は、ユーザーが直接操作するインターフェースを構築する一方で、APIを通じてバックエンドと密接に連携します。そのため、バックエンドのセキュリティ脆弱性は、一見すると無関係に思えるかもしれませんが、実はフロントエンドの設計や開発プロセスにも大きな影響を与えうるのです。
堅牢なWebアプリケーションを構築するためには、システム全体(フロントエンド、バックエンド、インフラ)のセキュリティリスクを理解し、それぞれがどのように連携して攻撃経路となりうるかを把握することが不可欠です。今回解説するNocoBaseの脆弱性は、APIを介した入力検証の不備と、それがどのように致命的なリモートコード実行(RCE)につながるかを示す、非常に良い事例と言えます。
脆弱性の概要:NocoBaseにおける認証済みリモートコード実行(RCE)
今回焦点を当てる脆弱性(GHSA-ghvf-qf6h-g8x5)は、Node.jsベースのローコードプラットフォームであるNocoBaseで発見されました。この脆弱性は、2つの異なる脆弱性が組み合わされることで、認証済み管理者によるリモートコード実行(RCE)を可能にします。深刻度は「High」に分類されており、攻撃者は有効な管理者セッショントークンを持つだけで、NocoBaseサーバー上で任意のコードを実行できてしまいます。
具体的には、以下の2つの脆弱性が連鎖します。
1. **Arbitrary File Write(任意のファイル書き込み):** ファイルアップロードの保存先ルートパスを、認証済み管理者が任意のディレクトリに設定できてしまう脆弱性。
2. **Local File Inclusion(ローカルファイルインクルージョン):** ユーザーが指定したパスのファイルを、Node.jsの`require()`関数で読み込ませて実行できてしまう脆弱性。
脆弱性1:Arbitrary File Write(任意のファイル書き込み)の詳細
最初の脆弱性は、`POST /api/storages:update` エンドポイントに見られます。このAPIは、NocoBaseのファイルマネージャープラグインにおけるストレージ設定を更新するために使用されます。通常、ファイルの保存先はアプリケーション内で定義された安全なパスに限定されるべきですが、このエンドポイントは`documentRoot`という設定値を、認証済み管理者からの入力値そのままに受け入れてしまいます。
具体的には、`packages/plugins/@nocobase/plugin-file-manager/src/server/storages/local.ts` 内の `getDocumentRoot()` 関数が、ストレージレコードの `documentRoot` フィールドを解決しますが、この値自体に対する検証が一切行われていませんでした。これにより、攻撃者は`documentRoot`をアプリケーションのルートディレクトリや、その他のシステム上の任意の絶対パスに設定できてしまいます。
その結果、次に `POST /api/attachments:upload` エンドポイントを使ってファイルをアップロードすると、設定された悪意のある`documentRoot`パス配下にファイルが書き込まれてしまいます。これは、ウェブサーバーの公開ディレクトリや、Node.jsプロセスが書き込み権限を持つ任意の場所に、攻撃者が任意のファイルを配置できることを意味します。
脆弱性2:Local File Inclusion(ローカルファイルインクルージョン)の詳細
2つ目の脆弱性は、`POST /api/pm:enable` プラグインマネージャーのエンドポイントに存在します。このAPIは、プラグインを有効化するために使用されますが、その際、`filterByTk`というクエリパラメータで渡された値を、何のパス検証も行わずに直接Node.jsの`require()`関数に渡してしまいます。
`require()`関数はNode.jsにおいてモジュールを読み込むための基本的な機能であり、絶対パスが指定された場合、そのパスにあるファイルをJavaScriptモジュールとして解釈し実行します。これにより、攻撃者は以下の2つのモードでシステムに影響を与えることができます。
1. **任意ファイルの読み取り (LFI):** `/etc/passwd`のような非JavaScriptファイルを指定した場合、Node.jsはこれをJavaScriptとして解析しようとし、構文エラーが発生します。このエラーメッセージにはファイルのコンテンツが含まれるため、システムのエラーログ(`system_error_YYYY-MM-DD.log`)をダウンロードすることで、ファイルの内容を間接的に読み取ることができます(エラーベースのLFI)。
2. **任意ファイルの実行 (RCE):** 脆弱性1でサーバーに書き込まれた悪意のあるJavaScriptファイルを指定した場合、`require()`関数によってそのファイルがNode.jsモジュールとして実行され、RCEが達成されます。
この脆弱性の根本原因は、`packages/core/server/src/plugin-manager/options/resource.ts`の`enable`アクションが、ユーザー入力である`filterByTk`を直接`app.runAsCLI(['pm', 'enable', ...keys])`に渡し、その後`packages/core/utils/src/requireModule.ts`内の`require(m)`が、この未検証の値をそのままモジュールパスとして使用してしまう点にあります。本来コードベースには安全なプラグインパッケージ名を検証する関数(`assertSafePluginPackageName()`)が存在するにもかかわらず、このHTTPアクションパスでは呼び出されていませんでした。
攻撃フローの連鎖:RCEへの道
これらの2つの脆弱性がどのように連鎖し、RCEに至るかを具体的に見てみましょう。
1. **準備(悪意あるJSファイルの作成):** 攻撃者は、サーバー上で任意のOSコマンド(例:`id; whoami; hostname`)を実行するJavaScriptファイルを作成します。
2. **任意のファイル書き込みの実行:** 認証済み管理者として、`POST /api/storages:update` エンドポイントを使い、ファイルアップロードの`documentRoot`をNocoBaseアプリケーションのルートディレクトリ(または書き込み可能な任意の場所)にリダイレクトします。
3. **悪意あるファイルのアップロード:** 次に、`POST /api/attachments:upload` エンドポイントを使って、ステップ1で作成した悪意あるJavaScriptファイルを、リダイレクトされた`documentRoot`のパスにアップロードします。
4. **ローカルファイルインクルージョンの実行(RCEトリガー):** 最後に、`POST /api/pm:enable` エンドポイントを使い、`filterByTk`パラメータにアップロードした悪意あるJavaScriptファイルの絶対パスを指定します。これにより、Node.jsの`require()`関数が悪意あるファイルを読み込み、サーバー上でコードが実行され、RCEが達成されます。
フロントエンドエンジニアが学ぶべき教訓
このバックエンドのRCE脆弱性から、フロントエンドエンジニアは何を学ぶべきでしょうか?
1. **入力検証の徹底(二重検証の原則):** クライアントサイドでの入力検証はユーザーエクスペリエンス向上に不可欠ですが、セキュリティの最終ラインは常にサーバーサイドにあるべきです。今回の脆弱性は、`documentRoot`や`filterByTk`のようなパラメータに対して、サーバー側で適切な検証が欠けていたことが根本原因でした。フロントエンドでバリデーションを実装しても、サーバーサイドで信頼せず再検証する「二重検証(Dual Validation)」の原則を理解し、チーム全体で徹底する文化を育みましょう。
2. **API設計におけるセキュリティ意識:** サーバーの設定やファイルシステムに影響を与える可能性のあるAPI(ファイルアップロード、ストレージ設定など)は、特に慎重に設計されるべきです。たとえ管理権限が必要な機能であっても、危険な設定を許容しないような設計や、厳格なバリデーションが不可欠です。APIの仕様を検討する際に、「この入力値が不正だった場合、システムにどのような影響があるか?」という視点を持つことが重要です。
3. **最小権限の原則 (Principle of Least Privilege):** NocoBaseのようなプラットフォームを利用する場合、各ユーザーやロールに必要最小限の権限のみを付与することが重要です。この脆弱性は管理者権限を悪用するものですが、不必要な広範な権限は常にリスクを高めます。フロントエンドの視点からも、ユーザーがアクセスできる機能やデータが適切に制限されているかを確認する意識を持つと良いでしょう。
4. **サプライチェーンセキュリティへの意識:** 自分が利用しているライブラリ、フレームワーク、そしてNocoBaseのようなプラットフォームの脆弱性情報には常に注意を払いましょう。最新のセキュリティパッチを適用することが最も効果的な対策です。フロントエンドのパッケージ管理(npm/Yarn)だけでなく、使用しているCMSやバックエンドシステムのアップデート情報も定期的に確認する習慣をつけましょう。
5. **開発環境でのセキュリティ意識:** ローカルで開発する際にも、安易に`documentRoot`のような設定を緩めたり、危険なAPIエンドポイントを検証なしで叩いたりしないよう意識しましょう。本番環境へのデプロイを想定し、常にセキュリティベストプラクティスに従うことが大切です。
まとめ
NocoBaseの認証済みRCE脆弱性は、バックエンドの実装ミスがどのように致命的な結果を招くかを示す強力な事例です。このケースは、APIを介してユーザーからの入力を受け取る際に、いかに徹底した検証が重要であるかを改めて示しています。
フロントエンドエンジニアも、ユーザーが操作するインターフェースの向こう側にあるAPIの挙動と、それらがシステム全体に与える影響について深い理解を持つことが、より安全で信頼性の高いWebアプリケーション開発への第一歩となります。常にセキュリティを意識し、開発プロセス全体で「安全」を優先する文化をチームで醸成していきましょう。