テンプレートエンジンの「魔法」が「悪魔」に変わる瞬間:SSTIの正体と防衛戦
現場でコードをレビューしていると、時々ぞっとする光景に出くわす。「ユーザーの入力値をテンプレートエンジンに直接投げ込む」という実装だ。開発者は「テンプレートエンジンがうまいことサニタイズしてくれるだろう」と楽観視するが、残念ながらテンプレートエンジンはセキュリティ製品ではない。
SSTI(Server-Side Template Injection)は、現代のWebアプリケーションにおいて、最も手軽に、かつ確実にサーバーの支配権を奪える攻撃の一つだ。今日は、なぜこれがRCE(リモートコード実行)に直結するのか、そしてどうすれば「絶対に」防げるのかを、現場の視点で解剖する。
—
1. なぜSSTIは「終わりの始まり」なのか
テンプレートエンジン(Jinja2, FreeMarker, Twigなど)は、本来「動的なHTMLを生成する」ための道具だ。しかし、これらは内部的に「式評価(Expression Evaluation)」を行っている。
もし、攻撃者が入力フォームやURLパラメータにテンプレートの制御構文({{ ... }}や#{ ... })を忍び込ませたらどうなるか。エンジンはそれを「表示用のデータ」ではなく「実行すべきコード」として解釈してしまう。
例えば、PythonのJinja2でこんなコードを書いたら即死だ。
絶対にやってはいけない実装例
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route(“/greet”)
def greet():
name = request.args.get(“name”)
# ユーザー入力を直接テンプレート文字列に埋め込んでいる
template = f”
Hello, {name}!
”
return render_template_string(template)
攻撃者は name パラメータに以下のようなペイロードを送り込む。
{{ self.__init__.__globals__.__builtins__['__import__']('os').popen('id').read() }}
これだけで、サーバー上のOSコマンド id が実行され、その結果がレスポンスとして返ってくる。これがSSTIによるRCEの恐怖だ。
—
2. 防衛の鉄則:エンジンの外側で「安全」を定義する
SSTIを防ぐための黄金律はただ一つ。「ユーザー入力をテンプレートの構文として処理させないこと」だ。
対策1:変数は必ず「コンテキスト」として渡す
テンプレートエンジンには、必ず値を安全に受け渡す仕組みがある。文字列結合でテンプレートを作ってはいけない。
【修正版:Python (Jinja2) のセキュアな実装】
安全な実装例
@app.route(“/greet”)
def greet():
name = request.args.get(“name”)
# 入力値をテンプレートの一部にするのではなく、変数として安全に渡す
return render_template_string(“
Hello, {{ user_name }}!
“, user_name=name)
この方法であれば、たとえ name に {{ 77 }} と入力されても、画面にはそのまま {{ 77 }} と表示されるだけで、コードは一切実行されない。これが正しい「分離」だ。
—
対策2:サンドボックス環境の過信を捨てる
「Jinja2のSandboxedEnvironmentを使えばいいのでは?」という質問を受けることがあるが、答えはNOだ。ブラックリスト方式の制限は、常に攻撃者のバイパス手法に追い抜かれる。攻撃者は、制限されていないオブジェクトや属性をパズルのように組み合わせて突破してくる。
防御は「制限」ではなく「構造」で行うべきだ。
—
対策3:WAFでの多層防御(参考設定)
アプリコードの修正が優先だが、万が一の脆弱性混入に備え、WAFでテンプレート構文を監視するのも有効だ。Nginx + ModSecurityの例を挙げる。
【Nginx/ModSecurity ルール例】
テンプレート構文のシグネチャを検知する(簡略化例)
SecRule ARGS “@rx (\{\{.\}\}|\$\{.\})” \
“id:10001,phase:2,deny,status:403,msg:’Potential SSTI Attack Detected'”
※ただし、これは誤検知のリスクがあるため、正規表現は慎重に調整してほしい。あくまで「最後の砦」だ。
—
3. セキュリティチーフからの「泥臭い」アドバイス
最後に、現場で戦う君たちに一つだけ伝えておきたい。
「動的テンプレート生成」という設計自体を避けるのが、最もコストパフォーマンスの良いセキュリティ対策だ。
- HTML生成はフロントエンドに任せる: サーバーサイドはAPI(JSON)を返し、テンプレート処理はReactやVue.jsなどのフロントエンドフレームワークにやらせる。これでSSTIの脅威は根本から消滅する。
- テンプレートエンジンのアップデートを怠らない: 過去の脆弱性(CVE)では、特定の条件下でのエスケープバイパスが報告されている。ライブラリは常に最新を保て。
- 静的解析ツール(SAST)をCIに組み込む: SonarQubeやBanditなどのツールは、テンプレートへの直接埋め込みを警告してくれる。人間がレビューする前に、まずは機械にチェックさせる習慣をつけよう。
セキュリティとは、完璧な製品を導入することではない。「悪意ある入力がシステムに入ってくることを前提に、その入力をいかに無害化し続けるか」という設計思想そのものだ。
今日紹介したコードは、今すぐ君のプロジェクトで見直すべき「最優先修正事項」だ。明日、本番環境で悪用される前に、コードベースを確認してくれ。健闘を祈る。
コメント