「テンプレートエンジンを使っているから開発が速い──でも、入力値の扱いを一つ間違えると、サーバーを丸ごと乗っ取られる可能性がある」と聞いたことはあるでしょうか。
サーバーサイドテンプレートインジェクション(SSTI: Server-Side Template Injection)は、WebアプリのテンプレートエンジンにユーザーI入力を評価させることで、サーバー上で任意コードを実行させる脆弱性です。SQLインジェクションと比べると知名度は低いものの、OWASPがA03(インジェクション)として分類する深刻な問題であり、バグバウンティ(脆弱性報奨金制度)でも「Critical」に分類されることの多い攻撃手法です。
この記事では、SSTIの仕組み・攻撃者の手口・実際の被害パターン、そして現場で今すぐ実践できる防御手順をコードレベルで具体的に解説します。
SSTIとは?なぜ深刻な脆弱性なのか
PythonのFlask(Jinja2)、PHPのTwig(Symfony)、JavaのFreemarkerなど、現代のWebアプリ開発ではテンプレートエンジンが広く使われています。テンプレートは「HTMLの骨格」であり、変数を埋め込んでページを動的に生成するために利用します。
正しい実装では、ユーザーの入力値は「変数」としてテンプレートに渡します。問題が起きるのは、テンプレート文字列そのものをユーザー入力で組み立てた場合です。
# 【正しい実装】ユーザー入力を変数として渡す return render_template("hello.html", name=request.args.get("name")) # 【危険な実装】ユーザー入力をテンプレート文字列に直接連結 name = request.args.get("name") template = "
Hello, " + name + "!
" return render_template_string(template) # ← ここが問題の根源
危険な実装の場合、攻撃者がURLに ?name={{7*7}} と送ると、Jinja2がこれを式として評価して「49」と出力します。この動作が確認できれば、攻撃者はテンプレートエンジンが入力を評価していると分かり、より危険なペイロードへとエスカレーションします。
SSTIがとりわけ深刻な理由は、SQLインジェクションがデータベース操作に限定されるのに対し、SSTIはOSコマンド実行・ファイル読み取り・内部ネットワーク探索(SSRF)にまで発展できる点です。攻撃が成功すれば、実質的にサーバーを乗っ取られる可能性があります。
攻撃の仕組み(敵を知る)
1. テンプレートエンジンの識別
攻撃者はまず「どのテンプレートエンジンが使われているか」を特定しようとします。各エンジンで式の評価方法が異なるため、応答を見て判別します。
| テンプレートエンジン | 言語・フレームワーク | 識別テスト(概念) | 応答パターン |
|---|---|---|---|
| Jinja2 | Python / Flask | {{7*’7′}} を送信 | 7777777(文字列の繰り返し) |
| Twig | PHP / Symfony | {{7*’7′}} を送信 | 49(数値として演算) |
| Freemarker | Java | ${“test”?upper_case} を送信 | TEST |
| Velocity | Java | #set($x=7)${x} を送信 | 7 |
2. オブジェクトチェーンを使った権限昇格
エンジンが特定できると、そのオブジェクト階層を辿ってシステムへのアクセス経路を探します。Jinja2の場合、Pythonの組み込みクラスの継承関係を遡ることで、OSのコマンド実行に相当する機能にアクセスできる経路が知られています。
具体的なexploit codeは掲載しませんが、概念としては「テンプレート内でオブジェクトのメソッドチェーンを辿り、サブプロセス生成やファイルI/Oに相当する処理を呼び出す」というものです。この種の攻撃はCTF(セキュリティコンテスト)や実際のバグバウンティでも頻出であり、確立された手法として広く知られています。
3. 実際の被害シナリオ
・任意ファイル読み取り: /etc/passwd・SSH秘密鍵・環境変数ファイル(.env)などをサーバーから外部へ漏洩させる
・RCE(任意コード実行): リバースシェルを張ることでサーバーを完全制御下に置く
・クラウドメタデータ窃取: AWS IMDSv1等のメタデータエンドポイントへアクセスし、IAMクレデンシャルを取得する
・データベース全件漏洩: アプリの接続情報(DB_PASSWORDなど)を.envから読み取り、DBに直接アクセスする
・内部ネットワーク探索: SSRFと組み合わせ、DMZの内部サービスやクラウドの内部APIを探索する
Portswigger(Burp Suite開発元)が2016年に発表したServer-Side Template Injection研究以降、主要テンプレートエンジンの攻撃手法は広く知られており、Burp SuiteのアクティブスキャンやOWASP ZAPでも自動検出の対象になっています。
具体的な防御手順
1. ユーザー入力をテンプレート文字列として扱わない(根本対策)
最も確実な対策は、テンプレートは「開発者が書いたファイル」であり、ユーザー入力はその中の「値」にとどめるという設計原則を徹底することです。
# Flask + Jinja2 の実装比較 # NG: render_template_string にユーザー入力を連結 @app.route("/hello") def hello_bad(): name = request.args.get("name", "") return render_template_string("
Hello, " + name + "!
") # OK: テンプレートファイルに分離し、変数として渡す # templates/hello.html にHello, {{ name }}
と記述 @app.route("/hello") def hello_good(): name = request.args.get("name", "") return render_template("hello.html", name=name)
この1点を守るだけで、SSTIリスクの大半を排除できます。「ユーザー入力=コード」として評価される状況を作らないことが原則です。
2. サンドボックスモードの活用
ユーザーが自分でテンプレートを定義できる機能(レポートビルダー・メールテンプレート編集など)を提供する場合は、テンプレートエンジンのサンドボックス機能を有効にします。
# Jinja2: SandboxedEnvironment を使用 from jinja2.sandbox import SandboxedEnvironment env = SandboxedEnvironment() # ユーザーが提供したテンプレート文字列を安全に評価 template = env.from_string(user_provided_template) result = template.render(display_name=sanitized_name)
サンドボックスは危険なオブジェクトへのアクセスを制限しますが、既知のバイパス手法も存在します。根本対策の代替にはならず、多層防御の一層として位置づけてください。
3. WAFでのペイロード検知
WAFを導入している環境では、SSTIのペイロードパターン({{・}}・${ などの式区切り文字)を含むリクエストを検知するルールを追加します。
・AWS WAF: AWSManagedRulesKnownBadInputsRuleSet にテンプレートインジェクション関連のルールが含まれます
・ModSecurity / OWASP CRS: REQUEST-932(RCE)ルールセットにテンプレートインジェクション検知が含まれます
・Cloudflare WAF: Managed Rules の「Injection」カテゴリで一部検知可能です
WAFはバイパスされる可能性があるため、コードレベルの修正が最優先です。WAFはあくまで「多層防御の最前線」として機能します。
4. 開発・テストフェーズでの検出
・静的解析(SAST): PythonはBanditでrender_template_stringの誤用を、PHPはPHPStan等でテンプレート生成の問題を検出できます
・動的スキャン(DAST): OWASP ZAPのアクティブスキャン・Burp Suite ProはSSTIの自動検出に対応しています
・コードレビューチェックリスト: render_template_string()・env.from_string()・Twig::createTemplate()などの危険な関数呼び出しを探す
・依存ライブラリの脆弱性管理: テンプレートエンジン自体にCVEが発見された場合に備え、DependabotやRenovate等で自動アップデート通知を設定する
中小企業でも今日からできること
「自社はWordPress(PHP)だから関係ない」と思われがちですが、WordPressプラグインやカスタムコードでPHPのテンプレート処理を独自実装している場合はSSTIのリスクが生じます。また、社内の受発注システム・顧客ポータル・レポート生成ツールなど、Python・PHPで自社開発したWebアプリこそ注意が必要です。
・ソフトウェア資産の棚卸し: 社内で稼働しているWebアプリの実装言語とテンプレートエンジンを把握しておく
・開発委託時のチェック: 要件定義・コードレビューの段階で「ユーザー入力をrender_template_string等に直接渡していないか」を確認事項に加える
・OWASP ZAP 無料スキャン: 情シス1人でもOWASP ZAPの無料版で基本的なSSTIスキャンを実施できます(認証が必要なページはログイン後に手動スキャン)
・エラーメッセージの管理: 開発環境のデバッグモードは本番・ステージングで必ずオフにし、詳細なスタックトレースを外部公開しない
予算をかけずに始めるなら、まず「テンプレート文字列にユーザー入力を連結しない」というルールを開発ガイドラインに明文化するところからです。
よくある誤解と注意点
【誤解1】「HTMLエスケープすれば防げる」
HTMLエスケープはXSSには有効ですが、SSTIには効きません。テンプレートエンジンはHTMLエスケープ処理の前に入力を評価するため、< や > への変換が行われても {{・}} の評価は防止されません。対策の本質は「評価される文字列にユーザー入力を含めない」です。
【誤解2】「内部ツールだから脆弱性は問題ない」
社内ネットワーク内でしかアクセスできないツールでも、フィッシングや標的型攻撃で社内PCが侵害された場合に悪用されます。「インターネットからアクセスされる前提」で対策することが、現代のゼロトラスト的な考え方です。
【誤解3】「フロントエンドは無関係」
Vue.js・Angularなどのクライアントサイドテンプレートでも、サーバーから受け取ったデータをテンプレートとして評価させてしまうケース(CSTI: Client-Side Template Injection)が存在します。問題の文脈はサーバーサイドと異なりますが、「ユーザー入力をコードとして評価しない」という原則は共通です。
本記事のまとめ
| ポイント | 内容 |
|---|---|
| SSTIの定義 | ユーザー入力がテンプレート文字列として評価されることで発生する脆弱性 |
| 深刻度 | RCE・ファイル漏洩・クラウドクレデンシャル窃取にまで発展しうる(OWASPはA03:インジェクション) |
| 主な対象 | Jinja2(Python)・Twig(PHP)・Freemarker・Velocity などのテンプレートエンジン |
| 根本対策 | ユーザー入力はテンプレートの「変数」として渡し、文字列として連結しない |
| 多層防御 | SandboxedEnvironment + WAFルール + SAST/DAST + コードレビューチェックリスト |
| 中小企業向け | 開発ガイドライン明文化 + 委託チェックリスト + OWASP ZAP 無料スキャン |
SSTIは「知っていれば防げる」典型的な脆弱性です。テンプレートエンジンの動作原理を正しく理解し、「ユーザー入力はコードではなく値として扱う」という原則を開発チームで共有することが、もっともコストパフォーマンスの高い対策です。
Linuxサーバー上でのアクセス制御や権限管理については、姉妹サイトLinuxMaster.JPでも詳しく解説しています。
テンプレートインジェクション以外の脆弱性も体系的に把握していますか?
SSTI・XSS・SQLi・CSRF――Webアプリの脆弱性は種類が多く、対策の優先度判断も難しいものです。
正しいセキュリティ知識を体系的に身につけたい方へ、メルマガで実践的なセキュリティ対策ノウハウをお届けしています。
