スタックトレースによる情報漏えい

エラー応答のスタックトレースによる内部情報の漏えい

説明

応答本文やHTTPヘッダーにerr.stackをそのまま含めると、内部のファイルパス、呼び出しの流れ、ライブラリやフレームワークの情報が漏えいするおそれがあります。攻撃者はそれを手掛かりに脆弱なコードを探し、追加の攻撃を具体化できます。

想定される影響

  • ファイルパス、ソースの行番号、モジュールやバージョンの情報が外部へ公開される可能性があります。
  • 呼び出し履歴が脆弱なコードやAPIの特定、インジェクション攻撃の調整に利用されるおそれがあります。
  • OSのユーザー名や絶対パスなどが、追加の侵害に役立つ情報を与える可能性があります。

対処方法

  • クライアントには一般的なエラーメッセージを返し、必要なスタックトレースはアクセスを制限したサーバーログにだけ記録してください。
  • Expressの中央エラーハンドラーへエラーを渡し、一貫したメッセージとステータスコードを返してください。
  • 応答本文、JSON、ヘッダーにerr.stackを含めないでください。
  • 本番では開発用のエラーページを無効にし、必要ならtraceIdで内部ログと対応付けてください。
  • ログのシークレットをマスキングし、アクセス権限と保存期間を制限してください。ログへ移すだけでログ自体が保護されるわけではありません。

例

変更前

javascript
const express = require('express');
const app = express();

app.get('/item/:id', async (req, res) => {
  try {
    // 処理中のエラーを想定
    throw new Error('DB query failed');
  } catch (err) {
    // BAD: 応答本文にスタックトレースを公開
    return res.status(500).send('Error occurred: ' + err.stack);
  }
});

module.exports = app;

変更後

javascript
const express = require('express');
const crypto = require('crypto');
const app = express();

// ルートの例
app.get('/item/:id', async (req, res, next) => {
  try {
    // 処理中のエラーを想定
    throw new Error('DB connection timeout');
  } catch (err) {
    return next(err); // 中央エラーハンドラーへ委譲
  }
});

// 中央ハンドラー: スタックはサーバーログだけに記録
app.use((err, req, res, next) => {
  const traceId = crypto.randomUUID();
  console.error(`[${traceId}]`, err.stack); // サーバー内部のログ
  res.status(500).json({ message: 'Internal Server Error', traceId }); // スタックを公開しない
});

module.exports = app;

変更前は応答本文にスタックを直接含めます。Node.jsは改行などを含むヘッダー値を拒否するため、この例では本文からの漏えいを示しています。

変更後は中央ハンドラーが一般的なメッセージとtraceIdだけを返し、スタックをサーバーログへ記録します。例のconsole.errorは本番用のログ保護を代替しません。機密情報を除去し、ログへのアクセスを制限してください。

参考資料