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

そのテンプレートエンジン、裏口が開いていませんか?:SSTIからRCEに至る「静かなる侵入」のメカニズム

現場でコードをレビューしていると、いまだに「テンプレートエンジンを使っているから安全だ」という甘い認識に遭遇する。しかし、Jinja2やThymeleafといった強力なツールは、適切に扱わなければ「攻撃者への招待状」に早変わりする。

今回は、Webアプリの盲点となりがちなSSTI(Server-Side Template Injection)について、なぜこれが「終わりの始まり」なのか、そしてどう防ぐべきかを、実戦的な視点で紐解いていこう。

—

1. SSTI:なぜテンプレートが「コマンド実行」に繋がるのか

テンプレートエンジンは、本来「静的なHTML」と「動的なデータ」を分離するためのものだ。しかし、開発者が「動的にテンプレートそのものを生成したい」と考え、ユーザー入力をテンプレート文字列として連結してしまうと悲劇が始まる。

攻撃のメカニズム

テンプレートエンジンには、ロジックを処理するための「式言語」が含まれている。攻撃者はこの式言語を悪用し、本来アクセス不可能な内部オブジェクト(__class__や__globals__など)を辿り、OSのコマンド実行関数を呼び出す。

例えば、PythonのJinja2で以下のようなコードがあったとする。

脆弱な実装例(絶対にしてはいけない)
@app.route(“/hello”)
def hello():
name = request.args.get(“name”)
# ユーザー入力をテンプレート文字列として直接レンダリングしている
template = f”Hello {name}!”
return render_template_string(template)

もし攻撃者が ?name={{config.__class__.__init__.__globals__}} と入力したらどうなるか? アプリケーションの内部設定がすべて露呈する。さらに進めれば、os.popen('ls').read() を実行させることも容易だ。これがSSTIによるRCE(リモートコード実行)の入口である。

—

2. 現場で使える防御戦略:根本解決は「分離」のみ

脆弱性を防ぐための黄金律は、「ユーザー入力をテンプレートのロジックとして解釈させない」ことだ。

Python (Jinja2) でのセキュアな実装

テンプレート文字列を動的に生成せず、変数は常に「引数」として渡すこと。

from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route(“/hello”)
def hello():
name = request.args.get(“name”, “Guest”)
# NG: render_template_string(f”Hello {name}”)
# OK: テンプレートは固定し、値をコンテキストとして渡す
return render_template_string(“Hello {{ name }}!”, name=name)

JavaScript (Node.js/EJS) の場合

Node.jsのテンプレートエンジンも同様だ。ejs.render()に直接ユーザー入力を渡してはいけない。

// セキュアな実装例
app.get(‘/render’, (req, res) => {
const userInput = req.query.name;
// テンプレートファイルはサーバー上の静的ファイルとして保持し、
// ユーザー入力はあくまで「データ」として渡すこと
res.render(‘profile’, { name: userInput });
});

—

3. 多層防御:アプリケーションの外側でもガードする

コードの修正が大前提だが、万が一の脆弱性混入に備えて、インフラ側でも「もしもの時の逃げ道」を塞いでおくべきだ。

WAFによるペイロード検知

AWS WAFなどのマネージドルールを有効化するのは当然として、SSTI特有の文字列({{ や __class__)をブロックするカスタムルールを検討しよう。

AWS WAF カスタムルール(JSON例):

{
“Name”: “Block-SSTI-Payloads”,
“Statement”: {
“ByteMatchStatement”: {
“SearchString”: “{{“,
“FieldToMatch”: { “QueryString”: {} },
“TextTransformations”: [{ “Priority”: 0, “Type”: “URL_DECODE” }]
}
}
}

最小権限の原則(IAM・コンテナ)

アプリケーションが実行されているコンテナには、必要最低限の権限しか与えてはならない。

  • 読み込み専用のファイルシステム: コンテナのルートディレクトリを読み取り専用にし、悪意あるスクリプトの書き込みを防ぐ。
  • 実行権限の制限: コンテナ内での curl や wget の実行を制限し、外部からの追加ペイロードダウンロードをブロックする。

—

最後に:セキュリティは「疑うこと」から始まる

SSTIの恐ろしさは、開発者が「便利機能」として意図的に実装してしまうケースが多い点にある。コードを書く際、ふと立ち止まって自問してほしい。

「この入力値は、テンプレートの構造を変えてしまう可能性はないか?」

「動的なテンプレート生成」が必要な場面は、実は極めて稀だ。もし必要だとしても、それはサンドボックス化された安全な環境で処理するか、そもそも別の設計(JSONによるデータ連携など)に切り替えるべきだ。

セキュリティは、魔法のようなツール一つで完結するものではない。日々の泥臭いレビューと、徹底した「境界線の管理」こそが、あなたのシステムを守る唯一の盾となる。

さあ、今すぐコードベースを検索して render_template_string や ejs.render がどう使われているか確認してみてほしい。その一行が、明日誰かの侵入口になるかもしれないのだから。

コメント

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