
Exploitability PoC for CVE-2026-9558 (SSTI Mautic Theme)
CVE-2026-9558 is a Server-Side Template Injection (SSTI) vulnerability in
Mautic's theme engine. Themes uploaded through the Theme Manager are ZIP
archives containing Twig templates, and in affected versions those
templates are rendered by the application's default Twig Environment
with no SandboxExtension policy attached.
Because Twig can invoke arbitrary PHP callables through filters such as
map, filter, and reduce, an authenticated user with theme upload
permissions can install a theme whose template runs an arbitrary system
command as soon as it is rendered — which happens automatically the first
time a user opens the email or landing page builder against that theme.
{{ ["id"] | map("system") | join }}
| Affected range | Fixed in |
|---|---|
| >= 1.3.0, < 4.4.13 | 4.4.13 (4.4.20 via ELTS) |
| >= 5.0.0, < 5.2.11 | 5.2.11 |
| >= 6.0.0, < 6.0.9 | 6.0.9 |
| >= 7.0.0, < 7.1.2 | 7.1.2 |
This lab targets 7.1.1, the last release before the fix.
Mautic's developer documentation states that theme templates are rendered in a restricted Twig sandbox:
Mautic renders User-uploaded Theme templates in a restricted Twig sandbox environment. The sandbox blocks certain functions and filters that could enable remote code execution, data leakage, or filesystem probing.
In affected versions, that sandbox policy does not exist. The fix
(7.1.2) introduces
ThemeSandboxPolicy.php,
a denylist-based SecurityPolicyInterface implementation that blocks the
map, filter, and reduce filters and a set of functions including
source() (arbitrary file read) and configGetParameter() (leaks DB
credentials and the application secret key). The fix's own inline comment
documents the exact payload shape used in this PoC:
private const DENIED_FILTERS = [
'map', // {{ ['id']|map('system')|join }} -> RCE
'reduce',
'filter',
];
Prior to this policy, no such restriction existed on any theme rendering path.
The chain was validated end to end against a low-privilege account with
only the Themes (create/edit) and Emails (create/edit) permission
groups — not Administrator — to match the PR:L precondition in the
advisory's CVSS vector.
system() calls embedded in the theme's
.twig file.config/local.php, exposing the database credentials and application
secret_key in plaintext.PDO connection from within the SSTI payload and
promote the low-privilege account to role_id 1 (Administrator),
bypassing Mautic's application-level permission checks entirely.uid=33(www-data) gid=33(www-data) groups=33(www-data)
'db_host' => 'mautic-db',
'db_user' => 'mautic',
'db_password' => 'mauticpass',
'secret_key' => 'a15d98da823d751f4a0a5219123f4afbd90eb254074b1cea07518a3c0db70bcd',
rows_updated=1
confirm: 2|priv-esc|1
The advisory states PR:L. This lab confirms the specific low-privilege
precondition in practice: reaching the render trigger requires both
theme upload permissions and email creation permissions — not a single
generic low permission. No workaround beyond the official fix restricts
this; the only documented mitigation is limiting core:themes:create to
trusted administrators.
cve-2026-9558-poc/
├── podman-compose.yml # MariaDB + Mautic 7.1.1-apache, isolated bridge network
├── Dockerfile.mautic # pins the official image to the vulnerable release
├── install.sh # non-interactive mautic:install bootstrap
└── sparse/
├── config.json # malicious theme manifest
├── sparse.zip # theme ready to be uploaded
└── html/
├── email.html.twig # SSTI payload template. Look at the end of file where there is the placeholder for injecting arbitrary commands.
├── ... # Other .twig files
└── .../ # Other directories (e.g css)
| Tool | Version | Notes |
|---|---|---|
| Podman | ≥ 4.0 | podman-compose required |
| curl | any | used by install.sh to poll readiness |
Exploitation in this PoC is performed manually through the Mautic web UI (Theme Manager upload → Email builder), matching the real attacker workflow: theme upload is a file operation, not an API call worth automating for this stage of the analysis.
podman-compose up -d
./install.sh
install.sh waits for the app container to accept connections, then runs
mautic:install non-interactively to bootstrap the first Administrator
account.
From the UI, create a custom role with only Themes (create/edit) and
Emails (create/edit) enabled, and a user assigned to that role. This
reproduces the PR:L precondition instead of testing as Administrator.
Log in as the low-privilege user. From Themes → Upload, upload a ZIP
built from sparse/config.json and sparse/html/page.html.twig (or a
modified copy of any built-in theme with the payload line added to its
.twig file).
From Channels → Emails → New, select the uploaded theme as the template, and open the builder. The payload executes as soon as the theme is rendered for preview.
podman-compose down -v
uid=33(www-data) gid=33(www-data) groups=33(www-data)
This repository is intended for educational purposes and local exploitability analysis only. All testing was performed against a self-hosted container environment. Do not run this PoC against systems you do not own or have explicit written authorization to test.
| Resource | Link |
|---|
| Advisory | GHSA-9fx4-7cmj-47vg |
| Fix (sandbox policy) | ThemeSandboxPolicy.php |
| Vulnerable repo | mautic/mautic |
| Full analysis — blog post | return-zero.dev/posts/cve-2026-9558 |