Aikido

なぜグローバル変数がNode.jsサーバーでデータ漏洩を引き起こすのか

セキュリティ

ルール
意図しない グローバル 変数の キャッシュを 避けてください。Node.js
および Python サーバーでは、 グローバル 変数は リクエスト間で
永続化され、 データ リークや レースコンディションを 引き起こします。

サポート言語: JavaScript、 TypeScript、 Python

はじめに

Node.jsサーバーのグローバル変数は、単一のリクエストだけでなく、プロセスのライフタイム全体にわたって持続します。リクエストハンドラがユーザーデータをグローバル変数に保存すると、そのデータは異なるユーザーからの後続のリクエストからもアクセス可能になります。これにより、ユーザーAのセッションデータ、認証トークン、または個人情報がユーザーBに漏洩するというセキュリティ脆弱性が生じます。

なぜ重要なのか

セキュリティ上の影響(データ漏洩): ユーザー固有のデータをキャッシュするグローバル変数は、リクエスト間のデータ漏洩を引き起こします。あるユーザーの認証状態、セッションデータ、または個人情報が他のユーザーに表示され、プライバシーとセキュリティの境界を侵害します。

競合状態: 複数の同時リクエストが同じグローバル変数を変更する場合、予測不能な動作が発生する可能性が高くなります。ユーザーAのデータが処理中にユーザーBのリクエストによって上書きされ、計算の誤り、状態の破損、またはユーザーが互いのデータを見てしまうことにつながる可能性があります。

デバッグの複雑さ: グローバル変数キャッシュによって引き起こされる問題は、リクエストのタイミングと並行処理に依存するため、再現が非常に困難です。バグは負荷がかかった本番環境で断続的に発生しますが、シングルスレッドの開発テストではめったに現れません。

メモリリーク: クリーンアップされずにデータを蓄積するグローバル変数は、時間とともに無制限に増加します。各リクエストはグローバルキャッシュや配列にさらにデータを追加し、最終的にサーバーメモリを使い果たし、プロセスの再起動を必要とします。

コード例

❌ 非準拠:

let currentUser = null;
let requestData = {};

app.get('/profile', async (req, res) => {
    currentUser = await getUserById(req.userId);
    requestData = req.body;

    const profile = await buildUserProfile(currentUser);
    res.json(profile);
});

function buildUserProfile(user) {
    return {
        name: currentUser.name,
        data: requestData
    };
}

誤っている理由: グローバル変数 currentUser と requestData はリクエスト間で永続化されます。複数のリクエストが同時に実行されると、ユーザーAの buildUserProfile() がまだ実行中に、ユーザーBのリクエストが currentUser を上書きし、ユーザーAがユーザーBのデータを見てしまう可能性があります。

✅ 準拠済み:

app.get('/profile', async (req, res) => {
    const currentUser = await getUserById(req.userId);
    const requestData = req.body;

    const profile = buildUserProfile(currentUser, requestData);
    res.json(profile);
});

function buildUserProfile(user, data) {
    return {
        name: user.name,
        data: data
    };
}

これが重要な理由:すべてのリクエスト固有のデータは、リクエストハンドラにスコープされたローカル変数に保存されます。各リクエストは、他の並行リクエストに漏洩しない分離された状態を持ちます。関数はグローバル状態にアクセスするのではなく、パラメータを通じてデータを受け取るため、競合状態が排除されます。

まとめ

すべてのリクエスト固有のデータは、フレームワークが提供するローカル変数またはリクエストオブジェクトに保持してください。グローバル変数は、設定、コネクションプール、読み取り専用キャッシュなど、真に共有される状態にのみ使用してください。グローバル状態が必要な場合は、適切な並行性制御を使用し、データがユーザー固有のものでないことを確認してください。

よくある質問

ご質問がありますか?

Node.jsでグローバル変数を使用しても安全なのはどのような場合ですか?

グローバル変数は、アプリケーション設定、データベース接続プール、コンパイル済みテンプレート、共有ユーティリティなど、すべてのリクエストに適用される読み取り専用データに対して安全です。リクエスト固有またはユーザー固有のデータをグローバルに保存しないでください。データをグローバルにキャッシュする必要がある場合は、適切にキー付けされ、アクセスがスレッドセーフであることを確認するか、Redisのような適切なキャッシュソリューションを使用してください。

明示的にグローバルではないモジュールレベル変数についてはどうですか?

モジュールレベル変数(ファイルスコープのconst、let、var)は、Node.jsのグローバル変数と全く同じように動作します。これらはすべてのリクエストで永続化され、すべての同時リクエストハンドラーで共有されます。同様のデータ漏洩および競合状態のリスクが適用されるため、モジュールレベル変数は明示的なグローバル変数と同様に注意して扱ってください。

ミドルウェアとルートハンドラー間でデータをどのように共有しますか?

フレームワークが提供するリクエストオブジェクトのプロパティを使用してください。Expressはreq.localsまたはreq上のカスタムプロパティを提供します。Fastifyにはrequest.decorateRequest()があります。これらのオブジェクトはリクエストスコープであり、リクエスト完了後に自動的にクリーンアップされ、リクエスト間のリークを防ぎます。

シングルトンパターンとクラスインスタンスについてはどうですか?

モジュールレベルのシングルトンインスタンスはグローバルステートです。それらがリクエスト固有のデータを保持する場合、同様の問題が発生します。シングルトンはステートレスであるか、設定のみを保持するように設計してください。ステートフルな操作の場合、リクエストごとに新しいインスタンスを作成するか、分離を保証するファクトリーパターンを使用してください。

開発中にこれらの問題を検出するにはどうすればよいですか?

異なるユーザーコンテキストを使用して、並行リクエストによるロードテストを実行します。競合状態やデータ漏洩は、シーケンシャルテストでは現れないことがよくあります。Apache Benchやautocannonのようなツールを使用して、並行負荷を生成します。あるリクエストのデータが別のリクエストに現れるタイミングを追跡するために、リクエストIDを含むロギングを追加します。

これはAWS Lambdaのようなサーバーレス関数に適用されますか?

部分的です。各Lambda呼び出しは新しい実行環境を取得しますが、コンテナは複数の呼び出しで再利用できます。同じコンテナを再利用する呼び出し間ではグローバル変数が保持されます。グローバル変数がリセットされることに依存しないでください。リクエストデータはローカルスコープに保持するという同じプラクティスに従ってください。

PythonのWSGI/ASGIアプリケーションについてはどうですか?

同じ原則が適用されます。Pythonのウェブサーバーはマルチスレッドまたは非同期で動作するため、モジュールレベルの変数はリクエスト間で共有されます。FlaskのgオブジェクトとFastAPIの依存性注入は、リクエストスコープのストレージを提供します。Djangoにはリクエストオブジェクトがあります。リクエストデータには、モジュールグローバルではなく、フレームワークが提供するメカニズムを使用してください。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です | スキャン結果は32秒で表示されます。