
CVE-2026-28496 を再現および検証するための Docker ベースのラボ。FOSSBilling の Twig レンダリングにおけるサーバーサイドテンプレートインジェクションの脆弱性で、脆弱なターゲットとパッチ適用済みのターゲットを比較します。
このリポジトリには、FOSSBilling の Twig テンプレートレンダリング動作に影響を与えるサーバーサイドテンプレートインジェクションの脆弱性である CVE-2026-28496 を再現・検証するためのローカル Docker ラボが含まれています。
FOSSBilling は、無料かつオープンソースの請求・クライアント管理プラットフォームです。0.8.0 より前のバージョンは、提供されたテンプレート式を評価できる安全でない Twig テンプレートレンダリング動作の影響を受けます。このラボでは、FOSSBilling 0.8.0 をパッチ適用済みの比較対象として使用しています。
このラボでは、以下の 2 つの FOSSBilling バージョンを比較します。
| サービス | FOSSBilling バージョン | 目的 | URL |
|---|---|---|---|
| vuln | 0.7.2 | 脆弱な比較対象 | http://localhost:8081 |
| patched | 0.8.0 | パッチ適用済みの比較対象 | http://localhost:8082 |
このローカルラボで実証される HTTP 検証パスは次のとおりです。```text Unauthenticated HTTP request in this local FOSSBilling 0.7.2 lab → POST /api/system/system/string_render → JSON body contains _tpl={{ 7*7 }} → vulnerable target renders the Twig expression → patched target does not expose the same tested API behavior
脆弱なターゲットでは、API呼び出しは次を返します:```json
{"result":"49","error":null}
パッチ適用済みターゲットでは、同じリクエストは次を返します:```json {"result":null,"error":{"message":"Unknown API call system/system/string_render","code":879}}
このラボは、FOSSBilling 0.7.2 と FOSSBilling 0.8.0 を使用して、脆弱性のある HTTP 動作とパッチ適用済みの HTTP 動作を検証します。
このラボは意図的にローカルの Docker サービスに限定されています。外部システムを対象とせず、Web シェル、マルウェア、永続化、外部コールバック、データベースダンプ、または破壊的なペイロードを含みません。
## 検証済みの事実
| 主張 | エビデンス | このラボでの確認方法 |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| CVE-2026-28496 は FOSSBilling 0.8.0 より前のバージョンに影響します。 | 公開された CVE およびアドバイザリのメタデータは、FOSSBilling 0.8.0 より前のバージョンが Twig SSTI の影響を受けると特定しています。 | References セクションを確認し、脆弱なターゲットバージョンとパッチ適用済みのターゲットバージョンを比較してください。 |
| FOSSBilling 0.7.2 は脆弱な比較対象として使用されます。 | 脆弱なサービスは公式の `fossbilling/fossbilling:0.7.2` Docker イメージから構築されます。 | `vuln/Dockerfile` を確認し、`docker compose ps -a` を実行してください。 |
| FOSSBilling 0.8.0 はパッチ適用済みの比較対象として使用されます。 | パッチ適用済みサービスは公式の `fossbilling/fossbilling:0.8.0` Docker イメージから構築されます。 | `patched/Dockerfile` を確認し、`docker compose ps -a` を実行してください。 |
| 脆弱な HTTP パスは `/api/system/system/string_render` です。 | 脆弱なターゲットは、`_tpl={{ 7*7 }}` に対して `result: "49"` を含む JSON を返します。 | `python3 poc/poc.py --url http://localhost:8081` を実行してください。 |
| パッチ適用済みターゲットは同じ HTTP 動作を公開しません。 | パッチ適用済みターゲットは `Unknown API call system/system/string_render` を返します。 | `python3 poc/poc.py --url http://localhost:8082` を実行してください。 |
| PoC は HTTP のみです。 | `poc/poc.py` は HTTP POST リクエストを送信し、Docker、Docker Compose、シェルコマンド、またはコンテナ API を呼び出しません。 | `poc/poc.py` を確認してください。 |
| ラボは Docker Compose 起動中に両方の FOSSBilling ターゲットを自動インストールします。 | ワンショットインストーラのサイドカーコンテナはセットアップを完了し、ステータス 0 で終了します。 | `docker compose ps -a` と `docker compose logs installer-vuln installer-patched` を実行してください。 |
| 脆弱なターゲットは無害な Twig 式をレンダリングします。 | ポート 8081 からの HTTP レスポンスは `{"result":"49","error":null}` です。 | 脆弱な PoC コマンドを実行してください。 |
| パッチ適用済みターゲットは、テスト対象の API パスを介して同じ式をレンダリングしません。 | ポート 8082 からの HTTP レスポンスは、コード `879` の JSON API エラーです。 | パッチ適用済みの PoC コマンドを実行してください。 |
## 前提と不明点
このラボでは、公開されている脆弱性調査により FOSSBilling 0.8.0 より前のバージョンが影響を受けると特定され、かつテスト対象のチェーンで使用されている最新の脆弱なリリースが 0.7.2 であるため、FOSSBilling 0.7.2 を脆弱な比較対象として使用しています。
このラボでは、公開されているアドバイザリのメタデータが 0.8.0 をパッチ適用済みバージョンとして特定しているため、FOSSBilling 0.8.0 をパッチ適用済みの比較対象として使用しています。
このラボは、次の観測可能な HTTP 動作に焦点を当てています。```text
POST /api/system/system/string_render
このJSONボディで:```json {"_tpl":"{{ 7*7 }}"}
このラボは、FOSSBilling 0.7.2 が HTTP API パスを通じて提供された Twig 式をレンダリングする一方、FOSSBilling 0.8.0 では同じ API 呼び出しが公開されていないことを示しています。
このラボは、FOSSBilling のすべてのテンプレートレンダリング機能をテストすることを主張するものではありません。CVE-2026-28496 は、アプリケーション内で利用可能なテンプレートレンダリング機能など、他の Twig レンダリングコンテキストにも関連しています。
このラボは、完全な未認証リモートコード実行チェーンを示すものではありません。ローカルの FOSSBilling 0.7.2 ターゲットで観察された未認証の HTTP 動作を検証し、FOSSBilling 0.8.0 と比較します。公開されている完全なチェーンには、ここで示す安全な Twig 式検証に加えて、追加の API 認可動作が関与します。
このラボでは以下を実演しません:
* リモートコマンド実行、
* 認証情報の抽出、
* データベースのダンプ、
* 拡張機能のインストール、
* 任意のファイル書き込み、
* 永続化、
* Web シェルのアップロード、
* 外部コールバック、
* ラボ外のシステムへの攻撃、
* または侵害後の活動。
## 根本原因の要約
CVE-2026-28496 の根本原因は、安全でない Twig テンプレートレンダリングです。
FOSSBilling は動的テンプレートのレンダリングに Twig を使用しています。影響を受けるバージョンでは、提供されたテンプレート文字列が、十分なサンドボックス制限なしに Twig レンダリングロジックに渡される可能性があります。
この脆弱な動作は次のように要約できます:```text
Input template string
→ FOSSBilling API receives _tpl
→ System\Api\Admin::string_render() reads _tpl
→ System\Service::renderString() receives the template string
→ Twig creates a template from the supplied string
→ Twig evaluates the expression
→ rendered output is returned in the HTTP response
この無害なテンプレート式の場合:```twig {{ 7*7 }}
脆弱なターゲットは式を評価して、以下を返します:```text
49
セキュリティ上の問題は算術評価に限定されません。算術評価は、このラボで使用される安全な可視シグナルに過ぎません。
よりセキュリティ上重要な問題は、サンドボックス化されていないTwigテンプレートが、テンプレートコンテキストで公開されているオブジェクトやメソッドにアクセスできる可能性があることです。公開された研究では、適切なテンプレートコンテキストオブジェクトが利用可能な場合、Twigテンプレートの実行が依存性注入コンテナを含むアプリケーション内部に到達できる、より影響度の高い経路が説明されています。
単純化した脆弱なモデルは次のとおりです:```text Template renderer → unsandboxed Twig expression → method/object access may be possible → application internals may become reachable → sensitive services may become reachable
FOSSBilling 0.8.0 のパッチ適用済み設計は、脆弱な動作を堅牢化しています。このラボでは、パッチ適用済みのターゲットは、テスト対象の API 呼び出しを公開しなくなりました:```json
{"result":null,"error":{"message":"Unknown API call system/system/string_render","code":879}}
セキュリティの教訓は:```text Template engines must not render user-controlled template strings in a privileged application context unless strict authorization and sandbox boundaries are enforced.
## ソースコード分析
脆弱なHTTP挙動は、FOSSBilling 0.7.2のソースコードパスに裏付けられています。
APIメソッドはリクエストデータから`_tpl`を受け取り、それをシステムサービスのレンダラーに渡します。
関連する脆弱なエントリポイント:```php
public function string_render($data)
{
if (!isset($data['_tpl'])) {
error_log('_tpl parameter not passed');
return '';
}
$tpl = $data['_tpl'];
$try_render = $data['_try'] ?? false;
$vars = $data;
unset($vars['_tpl'], $vars['_try']);
return $this->getService()->renderString($tpl, $try_render, $vars);
}
重要なデータフローは次のとおりです:```text HTTP request body → _tpl → System\Api\Admin::string_render() → System\Service::renderString()
In FOSSBilling 0.7.2 では、`renderString()` が指定された値をテンプレート名として読み込もうとします。それが失敗した場合、指定された値をテンプレート文字列として扱い、`createTemplateFromString()` に渡します。
簡略化した脆弱性フロー:```php
public function renderString($tpl, $try_render, $vars)
{
$twig = $this->di['twig'];
try {
$template = $twig->load($tpl);
$parsed = $template->render($vars);
} catch (\Exception) {
// $twig->load throws an exception when $tpl is a raw template string
$parsed = $this->createTemplateFromString($tpl, $try_render, $vars);
}