Expressでの反射型クロスサイトスクリプティング

Expressでの反射型クロスサイトスクリプティング

説明

リクエストのパラメーターやヘッダーなど、信頼できない入力を出力先の文脈に合わせてエンコードせずHTML応答へ挿入すると、反射型XSSが発生します。ブラウザーが値をHTMLやJavaScriptとして解釈し、攻撃者のコードが動作するおそれがあります。検索結果、エラーメッセージ、プロフィール画面などに<script> やイベントハンドラーが反映されると、読み取り可能な認証情報の窃取、なりすまし、リダイレクトなどにつながります。

想定される影響

  • 読み取り可能なCookieやトークンを盗まれ、アカウントを乗っ取られるおそれがあります。
  • ページの権限で任意のJavaScriptを実行される可能性があります。
  • 偽のログインフォームやリダイレクトにより、フィッシング被害につながるおそれがあります。
  • 画面の改ざんやフォームの自動送信で、意図しない操作を行わされる可能性があります。
  • メールアドレスやCSRFトークンなど、ページ内の機密情報を外部へ送信されるおそれがあります。

対処方法

  • HTML本文、属性、URLなど、出力位置に合うエンコードを行ってください。URLはスキームと宛先も検証してください。HTMLサニタイズはマークアップを許可するときの処理であり、テキストのエンコードとは異なります。
  • Pugなど自動エスケープを行うテンプレートエンジンを使い、信頼できない値をHTML文字列へ直接連結しないでください。
  • 可能なら res.json でJSONを返し、Content-Typeを適切に設定してください。クライアントでも応答の値を安全なテキストAPIで表示してください。
  • 想定する長さ、パターン、許可する文字を検証してください。
  • CSPや X-Content-Type-Options は追加の防御として使い、出力エンコードの代わりにしないでください。
  • Express、テンプレートエンジン、エンコード・サニタイズライブラリを更新してください。

例

変更前

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

// ユーザー入力をHTMLへ直接挿入
app.get("/greet", (req, res) => {
  const name = req.query.name || "guest";
  // nameが"<img src=x onerror=alert(1)>"の場合、スクリプトが実行される可能性がある
  res.type("text/html").send(`<h1>Hello ${name}</h1>`);
});

app.listen(3000);

変更後

javascript
const express = require("express");
const he = require("he"); // HTMLエンコード用ライブラリ
const app = express();

// 出力先の文脈に合わせてエンコード
app.get("/greet", (req, res) => {
  const rawName = typeof req.query.name === "string" ? req.query.name : "guest";
  const safeName = he.encode(rawName, { useNamedReferences: true }); // &, <, >, ", ' などをエンコード
  res.type("text/html").send(`<h1>Hello ${safeName}</h1>`);
});

// またはJSONを返す
app.get("/greet.json", (req, res) => {
  const name = String(req.query.name || "guest");
  res.json({ message: "Hello", name });
});

app.listen(3000);

解説:

  • 変更前: 入力をHTMLへ直接連結しています。挿入されたタグやイベントハンドラーによって悪意のあるJavaScriptが動き、アカウントの悪用やフィッシングにつながる可能性があります。
  • 変更後: 応答を返す前に he.encode で特殊文字をHTML本文用のエンティティへ変換します。JSONを返す方法でも、クライアント側で値を安全に表示する必要があります。

参考資料