説明
req.body や req.query など、ユーザーが制御するオブジェクト全体をテンプレートのlocalsへ渡すと、入力で描画オプションを操作されるおそれがあります。エンジン、アダプター、バージョンによっては、layout、filename、settings、partials、helpers などがファイルパスやテンプレートの動作を制御します。これらの操作により、ローカルファイルの露出や、構成によってはサーバー側のコード実行につながる可能性があります。{"layout":"../../../../etc/passwd"}、{"filename":"/etc/passwd"}、{"settings":{"views":"/"}} などは、その制御を変更しようとする入力例です。
想定される影響
- レイアウトやインクルードのパスを変更され、ローカルファイルが漏えいするおそれがあります。
- エンジンとオプションの組み合わせによっては、サーバー側でコードを実行される可能性があります。
- レイアウト、partials、helpersの挿入で、画面やアプリケーションの動作を変更されるおそれがあります。
- 設定の変更でデバッグ情報が露出し、別の攻撃につながる可能性があります。
対処方法
- テンプレートに必要なフィールドだけを選び、新しいオブジェクトへコピーしてください。オブジェクトのスプレッドも含め、
req.bodyやreq.query全体を渡さないでください。 layout、filename、views、partials、settings、helpers、runtimeOptionsなどの制御キーをユーザーが変更できないようにしてください。- スキーマでlocalsの型や値の範囲を検証し、想定外のフィールドを拒否してください。
- 対応する機能があれば、不要なコード評価を無効にし、インクルード先を固定ディレクトリへ制限してください。
例
profile テンプレートと、ビューエンジンのインストール・設定の残りの部分は省略しています。
変更前
javascript
const express = require("express");
const app = express();
app.set("view engine", "hbs");
app.use(express.json());
// ユーザーのオブジェクト全体をテンプレートのlocalsへ渡す
app.post("/profile", (req, res) => {
// { "layout": "../../../../etc/passwd" } などの制御キーを挿入される可能性がある
res.render("profile", req.body);
});
変更後
javascript
const express = require("express");
const app = express();
app.set("view engine", "hbs");
app.use(express.json());
// 許可したキーだけを新しいオブジェクトへコピー
const SAFE_KEYS = ["name", "bio", "location"];
function pick(obj, keys) {
const out = {};
for (const k of keys) {
if (typeof obj[k] === "string") out[k] = obj[k];
}
return out;
}
app.post("/profile", (req, res) => {
const locals = pick(req.body || {}, SAFE_KEYS);
res.render("profile", locals);
});
解説:
- 変更前: リクエスト本文全体を
renderに渡すため、テンプレートの動作を制御するキーも指定されるおそれがあります。ファイルアクセスやコード実行への影響は、エンジンと設定に依存します。 - 変更後: 必要なフィールドだけを新しいオブジェクトにコピーし、この経路で制御キーを渡されることを防ぎます。フィールドの長さ・値とテンプレート自体の安全性は別途確認してください。Expressが描画オプションを追加できるよう、渡すオブジェクトは凍結しません。