
Docker-based lab for reproducing and validating CVE-2026-28496, a Server-Side Template Injection vulnerability in FOSSBilling's Twig rendering, with vulnerable and patched comparison targets.
This repository contains a local Docker lab for reproducing and validating CVE-2026-28496, a Server-Side Template Injection vulnerability affecting FOSSBilling's Twig template rendering behavior.
FOSSBilling is a free and open-source billing and client management platform. Versions prior to 0.8.0 are affected by unsafe Twig template rendering behavior that can evaluate supplied template expressions. FOSSBilling 0.8.0 is used as the patched comparison target in this lab.
This lab compares two FOSSBilling versions:
| Service | FOSSBilling version | Purpose | URL |
|---|---|---|---|
| vuln | 0.7.2 | Vulnerable comparison target | http://localhost:8081 |
| patched | 0.8.0 | Patched comparison target | http://localhost:8082 |
The demonstrated HTTP validation path in this local lab is:
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
In the vulnerable target, the API call returns:
{"result":"49","error":null}
In the patched target, the same request returns:
{"result":null,"error":{"message":"Unknown API call system/system/string_render","code":879}}
This lab validates the vulnerable-versus-patched HTTP behavior using FOSSBilling 0.7.2 and FOSSBilling 0.8.0.
The lab is intentionally scoped to local Docker services. It does not target external systems and does not include web shells, malware, persistence, external callbacks, database dumping, or destructive payloads.
| Claim | Evidence | How to verify in this lab |
|---|---|---|
| CVE-2026-28496 affects FOSSBilling versions prior to 0.8.0. | Public CVE and advisory metadata identify FOSSBilling prior to 0.8.0 as affected by Twig SSTI. | Review the References section and compare the vulnerable and patched target versions. |
| FOSSBilling 0.7.2 is used as the vulnerable comparison target. | The vulnerable service is built from the official fossbilling/fossbilling:0.7.2 Docker image. | Inspect vuln/Dockerfile and run docker compose ps -a. |
| FOSSBilling 0.8.0 is used as the patched comparison target. | The patched service is built from the official fossbilling/fossbilling:0.8.0 Docker image. | Inspect patched/Dockerfile and run docker compose ps -a. |
The vulnerable HTTP path is /api/system/system/string_render. | The vulnerable target returns JSON with result: "49" for _tpl={{ 7*7 }}. | Run python3 poc/poc.py --url http://localhost:8081. |
| The patched target does not expose the same HTTP behavior. | The patched target returns Unknown API call system/system/string_render. | Run python3 poc/poc.py --url http://localhost:8082. |
| The PoC is HTTP-only. | poc/poc.py sends HTTP POST requests and does not call Docker, Docker Compose, shell commands, or container APIs. | Inspect poc/poc.py. |
| The lab auto-installs both FOSSBilling targets during Docker Compose startup. | The one-shot installer sidecar containers complete setup and exit with status 0. | Run docker compose ps -a and docker compose logs installer-vuln installer-patched. |
| The vulnerable target renders the harmless Twig expression. | The HTTP response from port 8081 is {"result":"49","error":null}. | Run the vulnerable PoC command. |
| The patched target does not render the same expression through the tested API path. | The HTTP response from port 8082 is a JSON API error with code 879. | Run the patched PoC command. |
This lab uses FOSSBilling 0.7.2 as the vulnerable comparison target because public vulnerability research identifies FOSSBilling versions before 0.8.0 as affected, and 0.7.2 is the latest vulnerable release used in the tested chain.
This lab uses FOSSBilling 0.8.0 as the patched comparison target because public advisory metadata identifies 0.8.0 as the patched version.
This lab focuses on the observable HTTP behavior of:
POST /api/system/system/string_render
with this JSON body:
{"_tpl":"{{ 7*7 }}"}
The lab demonstrates that FOSSBilling 0.7.2 renders the supplied Twig expression through the HTTP API path, while FOSSBilling 0.8.0 does not expose the same API call.
This lab does not claim to test every FOSSBilling template rendering feature. CVE-2026-28496 also relates to other Twig rendering contexts, such as template rendering features available inside the application.
This lab does not demonstrate the full unauthenticated remote code execution chain. It validates the unauthenticated HTTP behavior observed in the local FOSSBilling 0.7.2 target and compares it with FOSSBilling 0.8.0. The full public chain involves additional API authorization behavior beyond the safe Twig expression validation shown here.
The lab does not demonstrate:
The root cause of CVE-2026-28496 is unsafe Twig template rendering.
FOSSBilling uses Twig to render dynamic templates. In vulnerable versions, a supplied template string can be passed into Twig rendering logic without sufficient sandbox restrictions.
The vulnerable behavior can be summarized as:
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
For this harmless template expression:
{{ 7*7 }}
the vulnerable target evaluates the expression and returns:
49
The security issue is not limited to arithmetic evaluation. Arithmetic evaluation is only the safe visible signal used in this lab.
The more security-sensitive problem is that unsandboxed Twig templates may access objects and methods exposed in the template context. Public research describes a higher-impact path where Twig template execution can reach application internals, including the dependency injection container, when suitable template context objects are available.
The simplified vulnerable model is: