説明
ファイル名や投稿タイトルなどの保存された値を、適切な出力エンコードなしでHTMLとして表示すると、格納型XSSが発生します。攻撃者が保存したマークアップが、後からページを開いたユーザーのブラウザーで実行されるおそれがあります。例えば、アップロードされたファイルの名前をディレクトリ一覧のHTMLへ直接挿入すると、悪意のある内容が動作する可能性があります。
想定される影響
- スクリプトが読み取れるCookieやトークンを盗まれ、ログインセッションを乗っ取られるおそれがあります。
- ユーザーや管理者の権限を悪用される可能性があります。
- 画面の改ざんや偽の入力フォームで機密情報を収集されるおそれがあります。
- ブラウザーの権限を利用して内部APIを呼び出したり、状態を変更するリクエストを送られたりする可能性があります。
- 保存された内容を除去するまで、多数のユーザーに繰り返し影響が及ぶおそれがあります。
対処方法
- 保存された値をHTML本文、属性、URLへ挿入する前に、出力先の文脈に合わせてエンコードしてください。
- EJS、Pug、Nunjucksなどで出力エスケープを有効にし、信頼できない値をHTMLへ直接連結しないでください。
- 可能ならHTMLを組み立てず、
res.jsonでデータを返してください。 - ファイル名などの保存する入力は、用途に適した許可文字を使って検証・正規化してください。
- CSP、
X-Content-Type-Options、適切なHttpOnly・Secure・SameSiteCookie属性で被害を抑えてください。 - エンコードや検証には
escape-html、he、用途に合うvalidatorの関数など、実績のあるライブラリを使ってください。
例
変更前
javascript
const express = require("express");
const fs = require("fs");
const app = express();
// 保存されたファイル名をHTMLへ直接挿入
app.get("/files", (req, res) => {
fs.readdir("/var/app/uploads", (err, names) => {
if (err) return res.status(500).send("error");
const body =
"<h2>Files</h2><ol>" +
names.map((n) => "<li>" + n + "</li>").join("") +
"</ol>";
// BAD: n内の<script>などのタグが実行される可能性がある
res.send(body);
});
});
app.listen(3000);
変更後
javascript
const express = require("express");
const fs = require("fs");
const escapeHtml = require("escape-html"); // he、validatorなども選択肢
const app = express();
// HTMLの出力文脈に合わせてエンコード
app.get("/files", (req, res) => {
fs.readdir("/var/app/uploads", (err, names) => {
if (err) return res.status(500).send("error");
const safeItems = names
.map((n) => "<li>" + escapeHtml(n) + "</li>")
.join("");
const body = "<h2>Files</h2><ol>" + safeItems + "</ol>";
res.set("Content-Type", "text/html; charset=utf-8");
res.send(body);
});
});
// 別の方法: JSONを返し、クライアントの安全なテキストAPIで表示
app.get("/files.json", (req, res) => {
fs.readdir("/var/app/uploads", (err, names) => {
if (err) return res.status(500).json({ error: "error" });
res.json({ files: names });
});
});
app.listen(3000);
解説:
- 変更前:
fs.readdirで取得した信頼できないファイル名をHTMLへ連結し、res.sendで返しています。悪意のあるHTMLを含む名前が保存されると、一覧を開いたユーザーに格納型XSSの被害が及ぶ可能性があります。 - 変更後:
escape-htmlで各ファイル名をエンコードし、タグではなくテキストとして表示します。JSONを返す方法でも、クライアント側で値をHTMLとして再解釈しないようにしてください。