【入門編】 SSTI(Server-Side Template Injection)の悪用とサンドボックス回避 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
新人のIT担当者さんや、最近セキュリティを勉強し始めたという開発者の方、日々の業務本当にお疲れ様です。

セキュリティのニュースを見ていると、「脆弱性(ぜいじゃくせい)」や「攻撃」といった物騒な言葉がたくさん飛び交っていて、「なんだか難しそうだな…」「自分たちが書いたコードで大きなミスをしていないかな…」と不安になりますよね。

でも大丈夫です!一歩ずつ、身近な例えから紐解いていけば、攻撃者が何を狙っていて、どうやってそれを防げばいいのかがスッキリ見えてきます。

今回は、Webアプリの裏側でこっそり動いている便利な仕組み、「テンプレートエンジン」に潜む怖い落とし穴、SSTI(Server-Side Template Injection:サーバーサイド・テンプレート・インジェクション)について、優しく丁寧に解説していきますね。

—

1. 家の合い鍵と「テンプレートエンジン」の仕組み

まずは、私たちが普段作っているWebアプリケーションが、どうやって画面を作っているのかを「手紙の代筆サービス」に例えて考えてみましょう。

例えば、ユーザー一人ひとりに宛てたウェルカムメールを自動で大量に送るシステムを作るとします。
すべての文章を手書きするのは大変なので、こういう「ひな形(テンプレート)」を用意しておきますよね。

こんにちは、[名前]さん!
あなたの登録プランは [プラン名] です。

この [名前] や [プラン名] の部分に、データベースから引っ張ってきた実際のデータをカチャッとはめ込んで、きれいな完成形の文章を作る仕組みを「テンプレートエンジン」と呼びます。Jinja2(Python)や Thymeleaf(Java)などがその代表例です。

泥棒(攻撃者)が狙う「合言葉」の悪用

さて、この便利な代筆サービスですが、もし「ひな形を作る紙」に、お客さんが勝手に「特殊な命令」を書けるようになっていたらどうなるでしょうか?

イメージしてみてください。
家を建てるとき、郵便受けの隙間から「玄関の鍵を開けろ」という呪文を差し入れたら、鍵がカチャッと開いてしまうようなものです。

攻撃者は、このテンプレートエンジンに対して、ただの文字(名前やプラン名など)ではなく、サーバーを乗っ取るための特別な命令(コード)をこっそり混ぜ込んで送り込みます。これが SSTI の正体です。

—

2. 実際の攻撃はどんな風に行われるの?(Jinja2の例)

Pythonの有名なテンプレートエンジンである Jinja2 を例に、もう少し具体的に見てみましょう。
例えば、次のようなコードがWebアプリの裏側で動いていたとします。

from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route("/")
def index():
    # ユーザーがURLに入力した名前を受け取る(例: ?name=Taro)
    user_name = request.args.get("name", "ゲスト")
    
    # テンプレートのひな形に、ユーザーの入力をそのまま埋め込んでしまっている!
    template = f"<h1>こんにちは、{user_name}さん!</h1>"
    
    # テンプレートとしてレンダリング(解釈・実行)する
    return render_template_string(template)

一見すると、「ユーザーの名前を表示するだけの優しい機能」に見えますよね。
しかし、ここに大きな問題があります。render_template_string は、渡された文字列の中に {{ }} という記号を見つけると、「おっ、これは計算やプログラムの命令だな!」と解釈して、勝手に実行してしまうんです。

もし攻撃者が、次のようなURLにアクセスしたらどうなるでしょうか?

http://example.com/?name={{ 7 * 7 }}

画面には「こんにちは、49さん!」と表示されてしまいます。
数式が勝手に計算されてしまいましたね。さらに、計算だけでなく、サーバーのシステムファイルを読み込んだり、最悪の場合はサーバーそのものを乗っ取ってコマンドを実行したりするコードを送り込まれてしまうのです。これが「サンドボックス(安全な箱庭)の回避」へと繋がっていきます。

—

3. Thymeleaf(Java)の場合の怖さ

Javaの世界でよく使われる Thymeleaf でも似たような現象が起きます。
Thymeleafは、HTMLのタグの中に th:text="..." のような属性を書いてデータを埋め込みますが、もし動的に組み立てられたテンプレートの解釈部分にユーザーの入力が入り込んでしまうと、OGNL(Object-Graph Navigation Language)という強力な式言語が不正に実行されてしまいます。

例えば、次のような脆弱なコードがあったとします。

@GetMapping("/greeting")
public String greeting(@RequestParam String message, Model model) {
    // ユーザーからの入力をそのままテンプレート名や評価対象にしてしまっている危険な例
    // (※実際のThymeleafの仕様に基づいた簡略化したイメージです)
    return "templateName: " + message; 
}

Javaの環境でこのような式インジェクションを許してしまうと、OSのコマンド実行機能にアクセスされてしまい、サーバーの中身が丸見えになってしまうリスクがあります。まさに、家の防犯カメラの電源を外から勝手に切られるようなものです。

—

4. 安全なテンプレート設計と入力サニタイズのベストプラクティス

「うわ、テンプレートエンジンって怖いんだな…使わないほうがいいのかな?」
いいえ、そんなことはありません!テンプレートエンジンは、正しく使えばWebアプリの安全性を高めてくれる強力な味方です。

ここからは、実務の現場で今日からすぐに取り入れられる「防犯対策」を3つのステップで紹介します。

ステップ1:ユーザーの入力を「コード」として絶対に解釈させない

一番大切な原則は、「ユーザーから受け取ったデータを、テンプレートのプログラムとして実行させない(文字列としてただ表示するだけにする)」ということです。

Jinja2などの多くのテンプレートエンジンでは、標準でHTMLエスケープ(特殊文字の無効化)が行われますが、テンプレートの「構造自体」にユーザー入力を埋め込むことは絶対に避けましょう。

  • × やってはいけない書き方:
# ユーザーの入力をそのままテンプレートの文字列に埋め込んでいる
  template = "<div>" + user_input + "</div>"
  render_template_string(template)
  • 〇 安全な書き方:
# テンプレートのひな形は固定し、データは変数として渡す
  # (これなら、万が一ユーザーが {{ 7*7 }} と入力しても、ただの文字として画面に出るだけで計算されません)
  return render_template(
      "index.html", 
      user_name=user_input
  )

ステップ2:不要な機能やオブジェクトへのアクセスを制限する(サンドボックスの強化)

もしどうしても複雑なテンプレート評価が必要な場合でも、Pythonのビルトイン関数(__import__ や eval など)や、Javaのリフレクション機能にアクセスできないように、厳格な「サンドボックス(安全な隔離環境)」を設定します。
安全なライブラリやフレームワークのデフォルト設定を信じ、安易に危険なカスタムフィルターやカスタム評価器を追加しないことが重要です。

ステップ3:日々のコードレビューとセキュリティスキャンの実施

自分たちが書いたコードに、うっかり render_template_string のような危険なメソッドが使われていないか、チームで定期的にチェックしましょう。
CI/CDパイプラインに静的解析ツール(SAST)を組み込んで、怪しいコードの書き方を自動で検知できるようにするのも、現代のエンジニアの必須スキルです。

—

さいごに

今回は、SSTIのメカニズムと、テンプレートエンジンを安全に使うための基礎知識を解説しました。

セキュリティ対策というと難しく聞こえますが、要するに「信頼できない外からの来訪者(ユーザーの入力)に、家のマスターキー(テンプレートのコード実行権限)を絶対に渡さない」ということです。

この基本原則さえ守っていれば、怖がる必要はまったくありません。
一歩ずつ、安全で堅牢なアプリケーション作りを一緒に楽しんでいきましょう!次の現場でもぜひこの知識を役立ててくださいね。

コメント

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