こんにちは!現場の最前線でセキュリティと格闘しているエンジニアの皆さん。今日は、開発の現場で意外と見落とされがちな、でも一度食らうと致命傷になる「SSTI(サーバーサイドテンプレートインジェクション)」についてお話ししますね。
「難しそう…」と身構える必要はありません。まずは身近な例から紐解いていきましょう。
—
1. SSTIってなに?泥棒の例えで考えてみよう
皆さんの家には「鍵」がありますよね。玄関の鍵を閉めておけば、知らない人が勝手に入ってくることはありません。
Web開発におけるテンプレートエンジン(Jinja2やThymeleafなど)は、「手紙のテンプレートに、宛名だけ差し替えて大量に送る」ための便利なツールです。例えば、「こんにちは、{{ 名前 }}さん!」という枠組みに、ユーザーの入力した名前を当てはめて表示する仕組みです。
ここで問題になるのは、「テンプレートの中に、宛名以外の『命令文』を書き込める隙間があるかどうか」です。
もし、泥棒が宛名の欄に「宛名」ではなく「裏口の鍵を開けろ」というメモを忍び込ませて、それを家主(サーバー)が「あ、これは宛名じゃなくて指示書なんだね」と勘違いして実行してしまったら……これがSSTIの正体です。
攻撃者は、テンプレートエンジンが持つ「計算したり、別のプログラムを呼び出したりする機能」を悪用して、サーバーを乗っ取ろうとします。
—
2. なぜ起こるのか?:プログラムの「勘違い」
開発者が「ユーザーからの入力をそのままテンプレートに埋め込む」というコードを書くと、この事故が起きます。
例えば、PythonのJinja2でこんなコードを書いていないでしょうか?
危険なコードの例
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route(“/hello”)
def hello():
user_name = request.args.get(‘name’)
# ユーザーの入力をそのままテンプレート文字列として処理している!
template = f”
こんにちは、{user_name}さん!
”
return render_template_string(template)
もし攻撃者が ?name={{ 77 }} と入力したら、画面には「49」と表示されます。これならまだ可愛いものですが、これが「システムコマンドを実行せよ」という指示にすり替わると、サーバー上のファイルが全て盗まれたり、破壊されたりするんです。
—
3. どうやって防ぐ?:泥棒を入れない対策
対策は大きく分けて2つ。「入力値を信用しないこと」と「テンプレートの仕組みを分離すること」です。
対策①:入力値は「ただのデータ」として扱う
テンプレートエンジンには、ユーザー入力を「プログラムの命令」ではなく「ただの文字列」として安全に表示する機能が備わっています。
安全なコードの例
@app.route(“/hello”)
def hello():
user_name = request.args.get(‘name’)
# テンプレート文字列の中に直接埋め込まず、変数として渡す
return render_template_string(“
こんにちは、{{ name }}さん!
“, name=user_name)
これなら、泥棒が {{ 77 }} と入力しても、画面にはそのまま「{{ 77 }}」と表示されるだけで、プログラムは実行されません。これが最強の防犯対策です。
対策②:サンドボックス環境の検討
もし、どうしてもユーザーにテンプレートを書かせたい特殊なアプリを作るなら、「サンドボックス」という隔離された部屋を用意します。
砂場(サンドボックス)で遊んでいる限り、その子供(ユーザー)がどれだけ暴れても、外の家財道具(OSコマンドや重要なファイル)には手が届かないという仕組みです。権限を極限まで絞った環境でテンプレートを実行するように設計しましょう。
—
4. セキュリティ担当者からのアドバイス
「テンプレートエンジンを使っているなら、まずは {{ や }} といった特殊記号が、ユーザー入力に混じっていないかチェックする」だけでも、攻撃の芽を摘むことができます。
また、WAF(Web Application Firewall)を導入して、「テンプレート特有の構文を攻撃として検知・遮断する」のも、現代の防衛戦では非常に有効です。
まとめ:一歩ずつ進んでいきましょう!
1. 「ユーザー入力」と「テンプレートの構文」を混ぜない!
2. テンプレートエンジンに値を渡すときは、専用の変数バインド機能を使う!
3. もし不安なら、まずは入力値のバリデーション(チェック)を徹底する!
セキュリティは、「一度設定して終わり」ではなく、日々の開発習慣の積み重ねです。今日から皆さんの書くコードで、「ユーザーの入力は、宛名を書く場所にだけ入れてもらう」というルールを意識してみてください。
それだけで、皆さんのサーバーはぐっと安全になります。応援していますよ!
コメント