【入門編】 テンプレートインジェクション(SSTI)の仕組みとRCEへの発展 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。
新しい技術をキャッチアップしながら安全なシステムを作っていくのは、本当にエネルギーを使いますよね。

今回は、Webアプリケーションのセキュリティにおいて、近年とても注目されている「テンプレートインジェクション(SSTI)」という攻撃手法について、一緒にお話ししていきましょう。

「名前がなんだか難しそう……」「自分たちのシステムは大丈夫かな?」と不安になるかもしれませんが、安心してください!今回は、身近な「合鍵」や「郵便受け」の防犯にたとえながら、その仕組みと対策を一つずつ優しく紐解いていきます。

一緒に、安全なシステム作りの一歩を踏み出してみませんか?

—

テンプレートエンジンって、そもそも何だろう?

まずは、攻撃の話に入る前に、私たちが普段よく使っている「テンプレートエンジン」という便利な仕組みについておさらいしておきましょう。

例えば、ユーザーごとに「こんにちは、〇〇さん!」と名前を変えてメールを送ったり、Webページを表示したりするプログラムを作るとします。昔は、プログラムの中にHTMLタグとユーザーの名前を無理やり文字として繋ぎ合わせて書いていました。

// 古い書き方のイメージ(PHP)
echo "<h1>こんにちは、" . $userName . "さん!</h1>";

これだと、コードがごちゃごチャして読みにくくなりますし、書き方を間違えるとセキュリティ上の穴(脆弱性)が生まれやすくなります。

そこで登場したのがテンプレートエンジンです。これは、あらかじめ決まった「型(テンプレート)」を用意しておき、ユーザーの名前などの可変部分だけをパズルのピースのように埋め込んでいく仕組みです。

  • HTMLの型: <h1>こんにちは、{{ name }}さん!</h1>
  • 埋め込むデータ: name = "山田"
  • 完成形: <h1>こんにちは、山田さん!</h1>

お部屋の模様替えのときに、あらかじめ用意されたデザインの壁紙(テンプレート)に、お気に入りのポスター(データ)を貼っていくような感覚ですね。コードがすっきりと整理され、開発がとっても楽になる素晴らしい技術です。

—

テンプレートインジェクション(SSTI)ってどんな攻撃?

さて、ここからが本題です。この便利なテンプレートエンジンに潜む魔物が「SSTI(Server-Side Template Injection)」、日本語ではサーバーサイドテンプレートインジェクションと呼ばれているものです。

これはいったいどういう現象なのでしょうか?
身近な例えで考えてみましょう。

🏠 家のポストと「お手紙」のたとえ話

想像してみてください。あなたの家の玄関には、外から手紙を受け取るための「郵便受け(ポスト)」がついています。

  • 正常な利用: 郵便配達員さんが、あなた宛ての「お手紙(ただの文字データ)」をポストに入れていきます。あなたはそれを読んで「ふむふむ」と内容を理解します。
  • SSTIの攻撃: もし、悪意ある人が、郵便受けから手紙ではなく「遠隔操作できる時限爆弾や、合鍵を作るための型枠」を器用に差し込んだとしたらどうでしょうか?

家の中(サーバー)のシステムがそれをうっかり「これも読まなきゃいけない手紙だな」と勘違いして受け取ってしまい、その場で爆発(プログラムの実行)させてしまった……これがSSTIの正体です。

つまり、本来はただの「文字(データ)」として扱われるべきユーザーからの入力値の中に、テンプレートエンジンが特別に解釈してしまう「命令(コード)」を混ぜ込んでしまうことで、サーバー側で意図しないプログラムを実行させてしまう攻撃なのです。

—

実際にどんな風に攻撃されるの?(仕組みの解説)

もう少し技術的な視点から見てみましょう。
例えば、PythonのWebフレームワークであるFlask(Jinja2というテンプレートエンジン)を使った、簡単なアプリを想像してください。

ユーザーがURLの末尾に ?name=Taro のように名前を入力すると、画面に「Hello Taro」と表示する優しい機能があるとします。

# 脆弱なPythonコードのイメージ
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route("/")
def hello():
    user_name = request.args.get('name', 'Guest')
    
    # 危険な実装:ユーザーからの入力をそのままテンプレートとして読み込んでいる
    template = f"<h1>Hello {user_name}</h1>"
    return render_template_string(template)

ここで、開発者がうっかりやってしまいがちなのが、render_template_string() のように、ユーザーからの入力をそのままテンプレートの一部として処理してしまうことです。

もし攻撃者が、名前の代わりに以下のような呪文(Jinja2の構文)を入力したらどうなるでしょうか?

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

もしシステムが脆弱な場合、画面には Hello 49 と表示されてしまいます。文字を表示しているはずなのに、サーバー側で計算が行われてしまいましたよね。

恐怖の「RCE(リモートコード実行)」への発展

計算ができるということは、もっと危険なこともできてしまうということです。
Pythonの強力な機能を使って、サーバーの内部ファイルを覗き見たり、最悪の場合、サーバー上で自由勝手にコマンドを実行する(RCE:Remote Code Execution)ことすら可能になります。

例えば、Jinja2の環境によっては、以下のような入力でサーバー内のOSコマンド(例えば ls や whoami など)を実行し、システムを乗っ取られてしまうことがあります。

  • (※実際の攻撃コードの詳細は割愛しますが、オブジェクトの構造をたどってPythonの内部機能にアクセスし、システムコマンドを実行する関数を呼び出します)

これが、データベースのデータを盗まれたり、サーバーを丸ごとランサムウェアに乗っ取り返されたりする最悪のシナリオへと繋がっていくのです。

—

どうやって防げばいいの?一歩ずつ学ぶ対策のステップ

「うわぁ、なんだか怖くなってきちゃった……どうやって守ればいいんだろう?」
安心してください!適切な対策を知っていれば、この魔物からシステムをしっかり守ることができます。

一緒に、具体的な防衛策を見ていきましょう。

1. ユーザーの入力を「テンプレートのコード」として解釈させない

これが最も根本的かつ重要な対策です。
ユーザーから受け取った入力値は、あくまで「ただの文字データ(文字列)」として扱いましょう。

  • ❌ やってはいけないこと: ユーザーの入力をそのままテンプレートのソースコードの中に埋め込んで評価する(render_template_string に直接渡すなど)。
  • ⭕ 正しいこと: テンプレートファイル(index.html など)は固定の安全なものを用意し、ユーザーの入力は単なる「変数」としてテンプレートエンジンに渡す。
# 安全な実装のイメージ
@app.route("/")
def hello_safe():
    user_name = request.args.get('name', 'Guest')
    
    # テンプレートは固定ファイルを使用し、変数は安全に渡す
    return render_template(
        'index.html', 
        name=user_name
    )

これだけで、たとえユーザーが {{ 7 * 7 }} と入力しても、テンプレートエンジンはそれを計算せず、そのまま文字として画面に Hello {{ 7 * 7 }} と表示してくれます。お部屋のポストに鍵をかけたような状態ですね。

2. サンドボックス環境の適切な利用

どうしても動的なテンプレート処理が必要な高度なシステムでは、安全な「サンドボックス(砂場)」環境を作る必要があります。

サンドボックスとは、万が一おもちゃが壊れたり汚れたりしても、外の世界(部屋全体)には影響が及ばないように囲われた安全な遊び場のことです。
テンプレートエンジンがアクセスできる関数やオブジェクトを厳しく制限し、OSのコマンド実行機能などに絶対にたどり着けないように設定を固めましょう。

3. セキュリティヘッダーやフレームワークの機能を信じる

モダンなフレームワーク(Django, Rails, Laravel, Flaskなど)には、自動的にデータをエスケープ(無害化)してくれる機能が備わっています。
「自分で独自の便利な仕組みを作ろう」として生データを直接扱うのではなく、フレームワークが用意してくれている標準の安全な機能をそのまま素直に使うことが、結果的に一番の近道であり最強の防御になります。

—

まとめ

いかがでしたでしょうか?
テンプレートインジェクション(SSTI)は、便利さと引き換えに生まれてしまう、少し厄介な脆弱性です。

でも、怖がる必要はありません。

  • 「ユーザーからの入力は、決して信用してそのままプログラムの命令として実行させない」
  • 「データとプログラム(命令)の境界線をしっかりと分ける」

この基本さえ守っていれば、あなたのシステムはしっかりと守られます。

セキュリティの対策は、まるで毎日の戸締りのようなものです。最初は難しく感じるかもしれませんが、一つずつ習慣にしていけば、きっと自信を持って安全なサービスを開発できるようになりますよ。

これからも一歩ずつ、楽しく安全なエンジニアライフを歩んでいきましょう!

コメント

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