はじめに
Pug(旧 Jade)は、Node.js で広く利用されているテンプレートエンジンで、HTML を簡潔に記述でき、条件分岐・繰り返し・テンプレート継承など多くの便利な機能を備えています。しかし、Pug はテンプレート内部で JavaScript を直接実行できる設計であるため、扱い方を誤ると「サーバーサイド・テンプレート・インジェクション(SSTI)」につながる可能性があります。
本記事では、Pug が抱える仕組み上のリスクと、その背景にある Node.js の動作、さらに開発者が意識すべき対策について解説します。
Pug の脆弱性の根本:JavaScript インターポレーション
Pug の #{} 構文は、テンプレート内で JavaScript 式を評価し、その結果を描画します。
この機能自体は便利ですが、ユーザー入力をそのままテンプレートに渡すと、任意コードの実行につながる危険性があります。
例:テンプレート処理の確認
#{7 * 7}
値が 49 と表示されれば、テンプレートが実際に評価されている証拠です。
エスケープの限界
Pug には自動エスケープがありますが、これはあくまで HTML の XSS 対策であり、
テンプレート内の JavaScript 実行そのものは防げません。
特に !{} を使用するとエスケープが無効になるため、さらに注意が必要です。
Node.js のオブジェクトにアクセスできてしまう理由
Pug テンプレートは Node.js 上で実行されるため、テンプレートから Node.js のオブジェクト(例:process)にアクセス可能です。
これによって、モジュール読み込みや外部プロセスの起動など、本来テンプレートが触れるべきではない領域に入り込める構造となっています。
なぜ spawnSync('ls -lah') がうまく動かないのか?
Node.js の child_process.spawnSync() は下記の形式で利用します:
spawnSync(command, args, options)
-
command … 実行するコマンド名(例:
ls) -
args … 引数の配列(例:
['-lah']) - options … 文字コードや標準入出力の設定など
'ls -lah' のように コマンドと引数をひとまとめにした文字列 を渡しても、Node.js はそれを分解しません。
1つのコマンド名として解釈されてしまい、当然そのようなコマンドは存在しないため実行に失敗します。
この仕様は、コマンドインジェクションを防ぐ安全設計としても役に立っています。
#{root.process.mainModule.require('child_process').spawnSync('ls', ['-lah']).stdout}
開発者が取るべき安全対策
ユーザー入力をテンプレートに直接渡さない
必ずサーバー側でバリデーション・サニタイズを行い、想定外のデータが入らないようにする。
テンプレート内で JavaScript を実行しない設計にする
ロジックはコントローラ側に寄せ、テンプレートはあくまで「描画」のみに。
Pug の代わりにコード実行機能を含まないエンジンを検討
ejs, nunjucks, handlebars など用途に応じて検討する。
テンプレートを外部から動的に読み込まない
動的 include は脆弱性を拡大させる要因になる。
まとめ
Pug は便利で高機能なテンプレートエンジンですが、裏側では Node.js のオブジェクトへアクセスできてしまうほど強力です。そのため、ユーザー入力を不用意に組み込むと、重大な SSTI 脆弱性につながる危険があります。
正しい理解と安全な設計を行うことで、Pug を安心して活用できます。