GETクエリ文字列による機密情報の送信

GETクエリ文字列による機密情報の送信

説明

パスワード、トークン、APIキーをURLのクエリ文字列へ入れると、ブラウザー履歴、サーバーやプロキシのログ、共有リンク、監視システムに残る可能性があります。ポリシーや送信先によっては Referer ヘッダーでも送られます。HTTPSを使っても、URLの保存・共有経路はなくなりません。攻撃者が露出した秘密値を再利用し、アカウントやセッションを乗っ取ったり、APIを不正利用したりするおそれがあります。

想定される影響

  • 履歴、ログ、監視、キャッシュ、リファラーからパスワードやトークンが漏えいする可能性があります。
  • 盗まれたパスワードやアクセストークンの再利用により、アカウントやセッションを乗っ取られるおそれがあります。
  • APIキーなどを使った大量呼び出し、データ収集、予期しない課金につながる可能性があります。

対処方法

  • 機密情報をURLクエリへ入れず、処理に合ったPOST本文(req.body)や認証ヘッダー(Authorization)で送ってください。POSTのURLにも秘密値を入れないでください。
  • 対象ルートを本文やヘッダーから読み取るように変更し、該当するフォームには method="POST" を使ってください。
  • HTTPSを使い、本文や認証ヘッダーのログからも秘密値を除去・マスクしてください。
  • アプリケーションだけでなく、リバースプロキシや監視システムでも機密クエリの記録を制限してください。
  • 機密値を含むGETクエリは拒否してください。ただし、拒否前にプロキシがURLを記録する場合があるため、クライアントも修正してください。
  • リダイレクトURLや共有リンクに秘密値を含めないでください。

例

値の送信場所だけを比較する抜粋です。パスワード認証とトークン検証はコメントで省略しています。実装時には検証に成功した場合だけ成功応答を返してください。

変更前

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

// BAD: GETクエリからパスワードを取得
app.get("/signin", (req, res) => {
  const user = req.query.user; // 秘密値ではない例
  const password = req.query.password; // URLに露出
  // authenticate(user, password)
  res.send("signed in");
});

app.listen(3000);

変更後

javascript
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// GOOD: POST本文から取得
app.post("/signin", (req, res) => {
  const { user, password } = req.body; // 秘密値を本文で送信
  // authenticate(user, password)
  res.send("signed in");
});

// トークンにはAuthorizationヘッダーを使用する方法もある
app.post("/api/data", (req, res) => {
  const auth = req.get("Authorization"); // 例: 'Bearer <token>'
  // verifyBearer(auth)
  res.json({ ok: true });
});

app.listen(3000);

解説:

  • 変更前: パスワードがURLに含まれ、履歴やログに残ったり、ポリシーによってリファラーで送られたりする可能性があります。
  • 変更後: POST本文や Authorization ヘッダーから受け取るため、URLに秘密値を残しません。HTTPSとログの保護も必要です。

参考資料