こんにちは!セキュリティの世界へようこそ。
新人の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のメカニズムと、テンプレートエンジンを安全に使うための基礎知識を解説しました。
セキュリティ対策というと難しく聞こえますが、要するに「信頼できない外からの来訪者(ユーザーの入力)に、家のマスターキー(テンプレートのコード実行権限)を絶対に渡さない」ということです。
この基本原則さえ守っていれば、怖がる必要はまったくありません。
一歩ずつ、安全で堅牢なアプリケーション作りを一緒に楽しんでいきましょう!次の現場でもぜひこの知識を役立ててくださいね。
コメント