CyberFix Note
セキュアコーディング

サーバサイドテンプレートインジェクション(SSTI)の仕組みと対策

対象の目安: Webアプリ開発者 / 実務

ソウ攻撃・脆弱性リサーチ担当
・ 約16分で読めます
サーバサイドテンプレートインジェクション(SSTI)の仕組みと対策

テンプレートエンジンは、決まった書式である信頼できるテンプレートに、利用者ごとに変わる信頼できないデータを差し込んで最終的な出力を組み立てる仕組みです。名前を差し込む、金額を差し込むといった穴埋めを、安全に、かつ効率よく行うために使われます。サーバサイドテンプレートインジェクション(SSTI、Server-Side Template Injection)は、この穴埋めに使うはずの利用者入力が、差し込む値ではなくテンプレートそのものの一部として評価されてしまうときに発生します。エンジンがテンプレート言語の式を解釈できるため、多くの構成で最終的にサーバ上でのコード実行や情報の読み出しにまで至ります。

この記事では、SSTIがどのようにデータとコードの境界が壊れて起きるのか、なぜXSSより深刻になり得るのか、そして値として渡す実装を軸にどう防ぐのかを、Webアプリ開発者向けに整理します。特定エンジンで動く攻撃コードは載せません。挙動の確認を行う場合は、自組織が管理する検証環境に限って実施してください。

テンプレートエンジンにおけるデータとコードの境界

テンプレートエンジンは、あらかじめ用意した書式のなかに{{ name }}のような差し込み口を置き、レンダリング時にその口へ値を流し込みます。ここで守られるべき前提は、書式そのものは開発者が書いた信頼できるコードであり、流し込む値は利用者由来の信頼できないデータだという役割の分離です。値はあくまで文字として埋め込まれ、テンプレート言語の命令として解釈されることはありません。

SSTIは、この分離が壊れたときに起こります。利用者から受け取った文字列を、値として渡すのではなくテンプレート本体の一部として組み立ててしまうと、その文字列に含まれるテンプレート構文がエンジンによって命令として評価されます。差し込むはずのデータが実行されるコードに変わる、というデータとコードの境界の崩壊がSSTIの正体です。

PortSwiggerは、開発者がテンプレートに利用者入力を連結して埋め込むこと自体がSSTIの原因であり、安全な実装は利用者の値をパラメータとして分けて渡すことだと説明しています。

PortSwigger Web Security Academyは、SSTIを攻撃者がネイティブなテンプレート構文を使って悪意ある入力をテンプレートに注入し、それがサーバ側で実行される状態と定義しています。安全な実装では利用者の値をパラメータとして渡すのに対し、脆弱なコードはレンダリング前に利用者入力をテンプレート文字列へ直接埋め込んでいると指摘しています。

XSSとの違い

SSTIはしばしばXSSと混同されますが、コードが評価される場所が違います。XSSは、エスケープされない利用者入力がブラウザに届き、被害者のブラウザ上でスクリプトが実行される問題です。実行環境は利用者の画面のなかにとどまります。一方でSSTIは、テンプレートエンジンが動くサーバ側で式が評価されます。エンジンが持つオブジェクトや関数へ到達できると、アプリケーションの権限でサーバ側の処理を動かせてしまいます。

同じ入力箇所がXSSとSSTIの両方の入口になることもあります。たとえば利用者入力をテンプレートへ連結する実装では、ブラウザに渡ればXSS、エンジンに評価されればSSTIという形で、片方だけを塞いでも他方が残る場合があります。OWASPのテストガイドも、利用者入力をテンプレート文字列へ連結する実装がXSSとSSTIの双方につながり得ると示しています。したがって、出力エスケープでXSSを緩和しても、それはSSTIの根本原因である連結を解消したことにはなりません。

OWASP WSTGは、SSTIを利用者入力がテンプレートに安全でない形で埋め込まれ、サーバ上でのリモートコード実行につながる脆弱性と定義しています。利用者入力をテンプレート文字列へ連結するFlaskとJinja2の実装が、XSSとSSTIの双方の入口になる例を挙げています。

影響はリモートコード実行にまで及ぶ

SSTIの深刻さは、テンプレート言語が単なる文字列置換にとどまらず、式評価やオブジェクトへのアクセスといった能力を持つことに由来します。攻撃者がエンジンの内部オブジェクトや組み込み関数へ到達できると、そこを足がかりにサーバ上で任意のコードを実行し、バックエンドを掌握するところまで進み得ます。到達できる範囲によっては、ファイルの読み出しや設定情報の漏えい、内部ネットワークへのリクエスト送出によるSSRFといった被害にも広がります。

被害の大きさはエンジンの種類や、テンプレートから触れるオブジェクトの範囲で変わります。式評価の機能を絞ったエンジンでは影響が限定的なこともありますが、多くの汎用エンジンでは最悪の場合にリモートコード実行へ至るという前提で設計するのが安全です。

到達できる範囲起こり得る影響
テンプレート言語の式評価想定外の値の算出や条件分岐の書き換え
組み込みオブジェクトや関数ファイルの読み出し、設定や環境情報の漏えい
実行時オブジェクトへの連鎖サーバ上でのコード実行、システムの掌握
外向きのリクエスト機能内部リソースへのSSRF

PortSwiggerは、SSTIの影響としてリモートコード実行によるバックエンドサーバの完全な掌握や、機微なデータやファイルへの不正アクセスが起こり得ると述べています。影響が小さい場面もあるものの、多くの場合は致命的な侵害にさらされると位置づけています。

誤った実装パターン

SSTIを生む実装は、突き詰めると利用者入力をテンプレート文字列に連結してからレンダリングする、という一点に集約されます。次の疑似コードは、利用者から受け取った名前をテンプレート本体へ文字列連結してしまう誤ったパターンの概念を示したものです。動作する攻撃コードではなく、危険な構造を説明するための例です。

# 誤り: 利用者入力をテンプレート本体に連結してから評価する
name = request.get("name")
template = "こんにちは、" + name + " さん"   # name がテンプレートの一部になる
render_template_string(template)             # name 内のテンプレート構文が評価される

この形では、nameに含まれるテンプレート構文がエンジンに命令として解釈されます。利用者にテンプレートの中身を編集させる機能や、通知やメールの文面を利用者が指定できる機能などで、入力をそのままテンプレートとして評価する設計にすると同じ問題が生じます。原因は入力の中身ではなく、入力をコードの側に置いてしまう構造そのものにあります。

正しい実装は値として渡すこと

対策の核心は、利用者入力をテンプレート文字列へ連結せず、レンダリング時にコンテキスト変数(バインド値)として必ず分けて渡すことです。テンプレート本体は固定の書式として開発者が管理し、利用者由来の値は差し込み口へ流し込む値としてだけ扱います。こうすると、値のなかにテンプレート構文が含まれていても、それは命令ではなくただの文字として埋め込まれます。

# 正しい: テンプレートは固定、利用者の値は変数として渡す
template = "こんにちは、{{ name }} さん"       # 書式は開発者が管理する
render_template(template, name=request.get("name"))  # name は値として埋め込まれる

この分離を徹底するために、実装では次の点を守ります。

  1. 1

    テンプレートを静的に保つ

    テンプレート本体は開発者が管理する固定の資産とし、実行時に利用者入力から組み立てないようにします。文字列連結やフォーマット関数でテンプレートを動的に生成する箇所を洗い出し、値の受け渡しに置き換えます。

  2. 2

    利用者の値は変数として渡す

    利用者由来の値は、必ずレンダリングのコンテキスト変数として渡します。テンプレートの選択が必要な場合も、利用者入力をそのままパスやテンプレート名にせず、あらかじめ用意した候補への対応付け(許可リスト)で選びます。

  3. 3

    ロジックレステンプレートを選ぶ

    表示に必要な最小限しかできないロジックレスのテンプレートエンジンを選ぶと、仮に入力が評価されても実行できる範囲が狭まります。式評価や任意のメソッド呼び出しができるエンジンでは、テンプレートを利用者に触れさせない設計を前提にします。

  4. 4

    動的テンプレートが避けられない場合に備える

    利用者にテンプレート編集を許す要件がどうしても残る場合は、エンジンのサンドボックス機能で到達できるオブジェクトと機能を制限し、実行ユーザーの権限を最小化します。ただしサンドボックスは万能ではないため、後述の限界を踏まえて多層で守ります。

ロジックレス化とサンドボックスの限界

エンジンの選定はSSTIの被害範囲を大きく左右します。Mustacheのように表示のためのロジックを持たないエンジンは、テンプレート側で任意の式を評価できないため、SSTIが成立しても実行できることが限られます。OWASPのテストガイドやPortSwiggerも、ロジックレスのテンプレートが問題の予防に役立つと述べています。表示と処理の分離を設計段階で徹底できれば、テンプレートに危険な能力を持たせずに済みます。

一方で、利用者にテンプレート編集を許す要件が残る場合のサンドボックス化には注意が必要です。多くのエンジンはサンドボックスモードを備えますが、到達できるオブジェクトの連鎖をたどって制限を回避するサンドボックス脱出が数多く知られており、サンドボックスだけを最終防衛線にするのは危険です。サンドボックスを使う場合でも、エンジンとライブラリを最新に保ってパッチを適用し、実行ユーザーの権限を絞り、ネットワークやファイルへのアクセスを制限するといった多層の対策を重ねます。

OWASP WSTGは、SSTIがJinja2やTwig、FreeMarkerといったテンプレートエンジンに利用者入力が安全でない形で埋め込まれるときに生じると整理し、テンプレートから到達できる組み込みメソッドやオブジェクトの範囲が悪用の起点になると示しています。

弱点の分類と位置づけ

SSTIに関連する弱点は、CWE-1336(Improper Neutralization of Special Elements Used in a Template Engine、テンプレートエンジンで使われる特殊要素の不適切な無害化)として定義されています。これはテンプレートエンジンが外部からの入力を挿入または処理する際に、テンプレート式やコード指示として解釈され得る特殊要素を無害化しない、または誤って無害化する弱点です。CWE-1336はCWE-94(Improper Control of Generation of Code、コードインジェクション)の子として位置づけられ、テンプレート言語に特化したコードインジェクションの一種と理解できます。

つまりSSTIは、SQLインジェクションやOSコマンドインジェクションと同じく、データがコードとして解釈される注入系の弱点の仲間です。対策の考え方も共通しており、データとコードを構文レベルで分離し、利用者入力をコードの生成に混ぜないことが根本の防御になります。

CWE-1336は、テンプレートエンジンが外部からの影響を受ける入力を挿入または処理する際に、エンジンがテンプレート式やコード指示として解釈し得る特殊要素や構文を無害化しない弱点と定義され、CWE-94(コードインジェクション)の子として整理されています。

注入系の弱点との共通した考え方

SSTIをコードインジェクションの一種として捉えると、他の注入系対策との共通点が見えてきます。利用者入力を実行される文脈へ連結しない、値としてのみ扱う、という原則はOSコマンドインジェクションでも同じです。コマンドを組み立てる文字列に入力を混ぜず、引数として分けて渡す考え方は、テンプレートで値を変数として渡すのと発想が重なります。

あわせて読みたい

OSコマンドインジェクションの仕組みと根本対策。シェルに渡さず引数を分離する

また、入力を受け取る段階での検証とエスケープの位置づけを正しく理解しておくと、対策の順序を誤りません。入力検証は補助的な緩和であり、注入を根本から断つのは出力先の文脈に応じた分離だという整理は、次の記事にまとめています。

あわせて読みたい

入力バリデーションと出力エスケープの原則。入口で検証し、出口で文脈別にエスケープする

検出とレビューの勘所

SSTIの一般的な検出の考え方は、テンプレート言語で評価されると計算結果に変わるような無害な式を入力し、その計算が実行されるかを観察することです。たとえば掛け算のような算術式を送ったときに、文字がそのまま表示されるのではなく計算後の値が返るなら、入力が式として評価されている疑いが強まります。この判定は特定のRCEコードを使わずに行える一方、確認は自組織が管理する環境に限り、関連法令である不正アクセス禁止法などに触れない範囲で行います。

コードレビューでは、次の観点で連結の有無を確認します。

SSTIレビューの確認観点

  • 利用者入力をテンプレート文字列に連結、またはフォーマットで差し込んでいないか確認する
  • テンプレート名やテンプレートのパスを利用者入力から決めていないか(許可リストで選んでいるか)確認する
  • render_template_stringのように文字列からテンプレートを生成するAPIに利用者入力が流れていないか確認する
  • 利用者が指定する通知やメールの文面を、値ではなくテンプレートとして評価していないか確認する
  • 利用者の値がすべてコンテキスト変数(バインド値)として渡されているか確認する
  • 採用エンジンがロジックレスか、式評価が可能かを把握し、可能な場合はテンプレートを利用者に触れさせない設計になっているか確認する
  • 動的テンプレートが必要な箇所でサンドボックスと最小権限、パッチ適用を重ねているか確認する

CIとレビューで作り込みを防ぐ

SSTIは特定のフレームワークやAPIの使い方に現れやすいため、継続的インテグレーションの静的解析で作り込みを早期に見つけられます。テンプレートを文字列から生成するAPIや、利用者入力をテンプレート本体へ連結する箇所を検出するルールを用意し、プルリクエストの段階で警告します。テンプレートを扱う共通処理をラッパー化し、そのラッパー以外でテンプレート生成APIを直接呼ばない方針にすると、レビューで見るべき箇所が絞られます。

さらに、テンプレートに利用者入力を渡す機能を追加する変更では、値として渡しているかを差分レビューの必須確認項目にします。仕組みとしての静的解析と、設計意図を見る人手のレビューを組み合わせることで、注入経路の再発を抑えられます。OWASP Top 10の観点でSSTIを含むインジェクション全体を俯瞰しておくと、対策の優先順位を組織で共有しやすくなります。

あわせて読みたい

OWASP Top 10とは。開発者が押さえるべきWebの代表的リスクと対策を一気に理解する

まとめ

SSTIは、信頼できるテンプレートに信頼できないデータを差し込むというテンプレートエンジンの前提が崩れ、利用者入力がデータではなくテンプレートそのものとして評価されるときに発生します。テンプレート言語の式が評価されるため、多くのエンジンでリモートコード実行や情報漏えい、SSRFにまで至り得ます。根本原因は利用者入力をテンプレート文字列に連結することであり、対策の核心は入力を連結せず、コンテキスト変数として必ず分けて渡すことです。あわせて、ロジックレステンプレートの採用、最小権限、避けられない動的テンプレートでのサンドボックス化とパッチ適用を重ねます。出力エスケープはXSSの緩和策でありSSTIの根治ではない点、そしてサンドボックスには脱出が知られており単独の最終防衛線にはできない点を踏まえ、データとコードの分離を軸に多層で防ぎます。

出典・参考

この記事をシェア

関連する記事

セキュアコーディング

入力バリデーションと出力エスケープの原則。入口で検証し、出口で文脈別にエスケープする

セキュアコーディングの土台である入力バリデーションと出力エスケープを、それぞれの役割の違いから整理します。入口の検証は防御の一段目、出口の文脈別エスケープがXSSやインジェクションの根本対策である理由を、原理と実務の判断基準まで掘り下げます。

セキュアコーディング

OSコマンドインジェクションの仕組みと根本対策。シェルに渡さず引数を分離する

OSコマンドインジェクション(CWE-78)が外部入力をシェルコマンドとして解釈させて成立する機構、OWASPのインジェクション分類での位置づけ、そしてシェルを介さず引数を配列で渡す根本対策までを開発者向けに整理します。入力検証が補助にとどまる理由も扱います。