SSTI(サーバーサイドテンプレートインジェクション)の深淵:テンプレートエンジンを「凶器」にさせないための防衛術
やあ、エンジニア諸君。また一つ、現場でよく見かける「時限爆弾」の話をしよう。
アプリケーション開発において、Jinja2(Python)、Twig(PHP)、EJS(Node.js)といったテンプレートエンジンはもはや空気のような存在だ。しかし、多くの開発者は、テンプレートエンジンが「ただの文字列置換ツール」ではなく、「サーバー上で任意のコードを実行できる仮想マシン」であることを忘れている。
今回は、SSTI(Server-Side Template Injection)という、RCE(リモートコード実行)に直結する最も醜悪な脆弱性について、現場の知見を叩き込む。
—
1. なぜ「サニタイズ」だけでは足りないのか
多くのエンジニアは「ユーザー入力をエスケープすれば安全だ」と教わる。だが、SSTIの真の恐怖は、テンプレートエンジン自体が持つ「メタデータへのアクセス機能」にある。
例えば、Jinja2環境でユーザー入力をそのままテンプレートに埋め込むと、攻撃者は以下のようなペイロードを投げてくる。
攻撃者が送る悪意ある入力値
{{ self.__init__.__globals__.__builtins__.__import__(‘os’).popen(‘id’).read() }}
これは単なる文字列ではない。テンプレートエンジンのオブジェクト階層を辿り、Pythonの標準ライブラリ(osモジュール)をインポートし、シェルコマンド(id)を実行させるという、極めて洗練されたコード実行だ。WAFで や SELECT を弾いていても、この攻撃は「テンプレートの文法」に従っているため、いとも簡単に通過する。
---
2. 鋼鉄の防衛:サンドボックス化とロジック分離の鉄則
SSTIを防ぐために、小手先のブラックリスト方式は捨てろ。以下の2つの原則を叩き込んでほしい。
1. テンプレートには「データ」のみを渡す: ロジック(計算、DBアクセス、ファイル操作)はテンプレート外のコントローラー層で完結させる。
2. テンプレートエンジンを「サンドボックス」化する: 標準の実行環境を制限し、危険な属性(__globals__など)へのアクセスを禁止する。
---
3. 実践:セキュアな実装パターン
Python (Jinja2) の場合:環境の厳格な制限
Jinja2をデフォルト設定で使うのは自殺行為だ。SandboxedEnvironment を使用し、実行可能なメソッドを厳格に制限せよ。
from jinja2.sandbox import SandboxedEnvironment
セキュアな環境を構築
危険な操作(属性アクセスなど)を禁止した環境を作成する
env = SandboxedEnvironment()
テンプレートの読み込み
template = env.from_string("Hello, {{ name }}!")
実行時、もし攻撃者が __globals__ などにアクセスしようとすると
SecurityError が発生し、攻撃を未然に防ぐことができる
try:
print(template.render(name="World"))
except Exception as e:
print(f"攻撃を検知しました: {e}")
Node.js (EJS) の場合:ロジック分離の徹底
Node.jsのテンプレートエンジンで最も危険なのは、テンプレート内で require や process を呼び出せる設定にしていることだ。
対策:コンテキストの最小化
テンプレートには、描画に必要な最小限のオブジェクトのみを渡し、process や global は一切含めない。
const ejs = require('ejs');
// 危険:すべてのオブジェクトを渡すのは厳禁
// ejs.render(template, { ...global });
// 正解:必要なデータのみをホワイトリスト形式で渡す
const safeData = {
userName: "Alice",
items: ["apple", "banana"]
};
// テンプレート内では safeData.userName 以外にはアクセスできない
const output = ejs.render(template, { data: safeData });
---
4. インフラ・設定レベルでの防御(多層防御)
アプリ側の修正が困難なレガシーシステムを運用しているなら、WAFでの遮断は最低限の保険として機能する。
Nginx/WAFでのフィルタリング例 (ModSecurityルール案):
テンプレート特有の連続する波括弧 {{ や {% を含むリクエストを監視する。
ModSecurityのルール例(参考)
テンプレートの構文を悪用しようとする試みを検知する
SecRule ARGS "@rx \{\{.\}\}" \
"id:1001,phase:2,deny,status:403,msg:'SSTI Attempt Detected'"
※ただし、これはあくまで「気休め」だ。根本的な解決は、テンプレートに「ロジック」を書かせない設計に尽きる。
---
最後に:チーフからの提言
SSTIの脆弱性は、開発者が「便利さ」と「安全性」のトレードオフを怠った結果生まれる。テンプレートエンジンに「何でもできる力」を与えてはいけない。
- テンプレート内での関数呼び出しは禁止する。
- テンプレートに渡すデータは、型チェック済みのDTO(Data Transfer Object)のみにする。
- 開発環境でPoCを試す癖をつける。
君たちが書くコードは、サーバーの運命を握っている。テンプレートを単なる「表示用の器」として扱い、ロジックという猛獣はプログラムの純粋な層に閉じ込めておくこと。それが、この業界で長く生き残るための、最もコスト対効果の高いセキュリティ対策だ。
また現場で会おう。質問があればいつでも投げろ。
コメント