flawopen.com/Vulnerabilities/Server-Side Template Injection (SSTI)
Server-Side Template Injection (SSTI):原因と修正
「こんにちは、{{ name }}」という定型文で手紙を印刷する会社を想像してください。安全な方法では、固定された定型文と顧客名をデータとしてプリンターに渡します。危険な方法では、印刷前に顧客の文字列を定型文の指示に挿入します。その文字列をテンプレートエンジンが構文として解釈すると、Server-Side Template Injection(SSTI)になります。影響はエンジン、設定、公開されたオブジェクト、プロセス権限によって異なり、式の評価だけでOSコマンドが実行されるわけではありません。
主要概念とサブシステム用語
- テンプレートエンジン
- JinjaやTwigなど、テンプレート構文を解析し、テンプレートとコンテキストデータから出力を生成するサーバー側コンポーネント。
- テンプレートソースへのインジェクション
- 外部テキストが解析前にソースへ組み込まれ、データではなくテンプレート構文として解釈される不備。
- コンテキストデータ
- 信頼できる固定テンプレートに別途渡す値です。適切に分離すると、
{{ 7 * 7 }}のような値をソースとして再解析しません。 - CWE-1336
- テンプレートエンジンで使われる特殊要素の不適切な無害化。外部入力をテンプレート構文として危険に処理する弱点です。
攻撃の流れを段階ごとに解説
攻撃者の入力がテンプレート処理へ届く
フォーム入力、保存済みプロフィール値など、外部から影響される文字列がテンプレートを作成・コンパイルするコードへ届きます。問題は値の通常表示ではなく、ソースの構築です。
アプリがデータとテンプレート指示を混ぜる
静的テンプレートと別のコンテキスト値を使わず、信頼できない文字列をテンプレートソースへ埋め込みます(例:f-stringをrender_template_stringに渡す)。
エンジンが結合されたソースを解析する
エンジンは言語と設定に従って式を解釈します。無害な算術式は評価の確認に使えますが、それだけでファイル、秘密情報、システムコマンドへのアクセスは証明されません。
影響はエンジンと実行環境に依存する
追加の影響はアクセス可能なオブジェクトや関数、サンドボックスの動作、エンジン制限、Webプロセスの権限に左右されます。一部の設定でRCEは可能ですが、すべてのSSTIで自動的に起きるわけではありません。
仕組みと根本原因
SSTIは、外部から影響されるテキストを解析前にテンプレートプログラムの一部にしてしまうことで発生します。Flask/Jinjaでは、リクエスト値から新しいソース文字列を作り、render_template_string()に渡すのが危険なパターンです。HTMLエスケープは出力を保護するもので、信頼できないテンプレートソースをデータに変えるものではありません。固定テンプレートを読み込み、値をコンテキストとして別に渡すのが修正です。ユーザーにテンプレートを作らせる場合は、最小限のデータだけを公開し、リソース制限のある分離workerでレンダリングしてください。サンドボックスは多層防御であり、秘密や強力なアプリケーションオブジェクトを公開する理由にはなりません。
# VULNERABLE: request data becomes template source before parsing.
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/greet")
def greet():
name = request.args.get("name", "Guest")
# A value such as {{ 7 * 7 }} is interpreted by the template engine.
source = f"<h1>Welcome, {name}!</h1>"
return render_template_string(source)
# HARDENED: fixed template source; request data stays a context value.
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/greet")
def greet():
name = request.args.get("name", "Guest")
return render_template("greet.html", name=name)
# templates/greet.html is trusted, static source:
# <h1>Welcome, {{ name }}!</h1>
攻撃シーケンスと概念例
- 1入力元: 外部から影響される値がテンプレート構築処理に届きます。
- 2危険な結合: アプリが値を別個のコンテキストデータとして渡さず、テンプレートソースに埋め込みます。
- 3解釈: エンジンが結合済みソースを解析します。許可されたテスト環境では無害な算術式で評価を確認できます。
- 4条件付きの影響: 追加アクセスはエンジン、公開オブジェクト、設定、プロセス権限次第です。式の評価だけでRCEと判断しないでください。
エンジニア向け防御・予防ルール
- ✓ソースを固定する: 信頼できるテンプレートをファイルまたは管理されたloaderから読み込みます。ユーザー入力はコンテキスト値だけに渡し、リクエストデータをソースに連結しないでください。
- ✓SSTIとHTMLインジェクションを区別する: HTMLエスケープは出力コンテキストを保護しますが、信頼できない入力がテンプレートソースとして解析される流れは修正しません。
- ✓ユーザー作成テンプレートを制限する: 必要な機能なら、用途に合ったエンジンのサンドボックスを使い、最小限のデータモデルだけを公開し、リソース制限付きの分離workerでレンダリングします。エンジンの安全上の注意に従ってください。
- ✓影響範囲を制限する: レンダラーをroot以外で動かし、ファイル、ネットワーク、秘密情報へのアクセスを最小限にします。これは封じ込めであり、主たる修正の代わりではありません。
- ✓回帰テストを追加する:
{{ 7 * 7 }}のような無害な文字列をデータとして渡し、データのまま扱われることを確認します。テンプレートが信頼済みloaderからだけ読み込まれることも検証します。