【実務・中級編】テンプレートインジェクション(SSTI):サーバーサイドテンプレートエンジンの悪用 – アプリケーションセキュリティ & 安全な開発防御ガイド

テンプレートインジェクション(SSTI)の深淵:なぜ「ただの文字列」がサーバーを乗っ取るのか

こんにちは。現場で日々、防御側の視点からシステムを磨き上げているエンジニア諸君。

今日は「SSTI(Server-Side Template Injection)」について話そう。多くのエンジニアが「テンプレートエンジンを使っているから安全だ」と信じ込んでいる。だが、その信頼こそが最も危険な脆弱性の入り口になる。

SSTIは、単なるWebアプリのバグではない。「データとして扱うべき入力を、エンジンが実行コードとして解釈してしまう」という設計上の致命的な勘違いだ。一度ここを突破されると、OSコマンドの実行(RCE)から環境変数のダンプ、内部ネットワークへの侵入まで一直線だ。教科書的な「入力をサニタイズしろ」といった抽象的なアドバイスは忘れて、現場で通用する「勝ち筋」の話をしよう。

—

1. なぜ「Jinja2」はハッカーに愛されるのか

PythonのJinja2やFlask環境において、開発者がよくやるミスがこれだ。

【危険なコード例】絶対に真似してはならない
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route(“/greet”)
def greet():
name = request.args.get(“name”, “Guest”)
# ユーザー入力を直接テンプレート文字列に埋め込んでいる!
template = f”

Hello, {name}!

”
return render_template_string(template)

このコードに対し、攻撃者はこのようなリクエストを送る。
/greet?name={{77}}

もしページに Hello, 49! と表示されたら、そこはSSTIの温床だ。これはJinja2が {{ ... }} の中身を式として評価している証拠であり、攻撃者はこの「計算機」を使って、Pythonのクラス構造を辿り、最終的に os.popen('id').read() を実行するペイロードを送り込んでくる。

—

2. SSTIを確実に防ぐための「鉄則」

現場でSSTIを完全に無力化するには、以下の2つの防衛ラインを必ず守れ。

防衛ライン①:入力値とテンプレートを物理的に分離する

最も確実な対策は、テンプレートエンジンの「式評価機能」にユーザー入力を渡さないことだ。テンプレートはあくまで「表示用」であり、ユーザー入力は「変数」として渡すべきだ。

【セキュアな実装例:Python/Flask】

@app.route(“/greet”)
def greet():
name = request.args.get(“name”, “Guest”)
# テンプレート文字列に埋め込むのではなく、引数として渡す!
# これにより、nameは単なる「文字列」として扱われ、評価は一切行われない。
return render_template_string(“

Hello, {{ name }}!

“, name=name)

防衛ライン②:サンドボックスの限界を理解する

多くのテンプレートエンジンにはサンドボックス(安全な実行環境)があるが、言語の仕様を完全に制限しきるのは不可能に近い。特にPythonのような動的言語では、__subclasses__() を呼び出すだけで、いとも簡単にサンドボックスから脱出できる。

だからこそ、「サンドボックスの設定で守る」という発想を捨て、「ユーザー入力を評価させない」という設計に徹するんだ。

—

3. インフラレイヤーでの多層防御(WAF/Nginx)

アプリケーション側の修正がすぐにできない場合、あるいはゼロトラストの観点から念には念を入れたい場合は、WAFでガードする。

【AWS WAFのルール設定例(イメージ)】
SSTIのペイロードには、{{ や {% といったテンプレート特有の記号が必ず含まれる。これらを検知対象にする。

  • Match criteria:
  • {{ または {%
  • __class__ (Pythonオブジェクトの構造を辿る際に必須)
  • os.popen や import といった危険な関数名
  • Action: Block

また、Nginxで特定の特殊文字を含むリクエストを弾くのも有効だ。

nginx.conf の locationブロックに追記
URL内にテンプレートの特殊記号が含まれる場合、即座に403を返す
if ($query_string ~ “(\{\{|\{\%|\}\}|\%\})”) {
return 403;
}

—

4. チーフからの教訓:最後に

SSTIのような攻撃は、開発者の「ちょっとした便利機能の実装」から始まる。コードを書くとき、常に自分に問いかけてほしい。

> 「この変数は、テンプレートエンジンが解釈すべき『命令』なのか? それとも単なる『表示用データ』なのか?」

もし後者であれば、render_template_string のような動的なテンプレート生成を避け、静的なテンプレートファイルを利用する設計に切り替えるべきだ。

セキュリティとは、穴を塞ぐことではない。「穴が開かない構造」を設計することだ。現場での実装において、この原則を忘れないようにしてくれ。

もし実装中に「これ、本当に安全か?」と迷うような複雑なケースがあれば、いつでも相談に来い。君たちの書くコードが、誰かの脅威にならないことを祈っている。

コメント

タイトルとURLをコピーしました