Shopware 6: Mapping Your Way to RCE via Twig Type Juggling
Jan 14, 2026·5 min read
Executive Summary (TL;DR)
Shopware tried to sandbox Twig by checking if function names were allowlisted strings. They forgot that PHP functions can also be called as arrays (e.g., `['Class', 'Method']`). This vulnerability exploits that oversight to bypass the sandbox completely, turning a simple template rendering engine into a remote shell.
A critical logic flaw in Shopware 6's Twig SecurityExtension allows attackers to bypass the function allowlist. By leveraging PHP's loose typing and passing array-based callables to the 'map' filter, attackers can evade security checks and execute arbitrary PHP methods, leading to Remote Code Execution (RCE).
The Sandbox Mirage: Trusting the Wrong Types
Shopware 6 relies heavily on Twig for its frontend and email templating. It’s a powerful engine, but giving users—even admins—access to raw Twig is basically handing them a loaded gun. To prevent users from shooting the server in the foot, Shopware implemented a SecurityExtension. Its job is simple: act as a bouncer, checking every function call within a template against a strict VIP list (the allowlist).
The logic seems sound on paper. If a user tries to run system('rm -rf /') inside a template, the extension intercepts the call, checks if system is on the list (it isn't), and throws a security exception. This is the standard "sandbox" approach: default deny, explicitly allow.
However, sandboxes in dynamic languages like PHP are notoriously difficult to get right. They require the defender to anticipate every possible way code can be executed. In this case, the developers anticipated strings. They forgot that in PHP, a "callable" is a shapeshifting demon that can take many forms.
The Flaw: A fatal assumption in `is_string()`
The vulnerability lies in src/Core/Framework/Adapter/Twig/SecurityExtension.php. Specifically, Shopware overrode the map filter. The map filter is standard Twig functionality that applies a function to every element of an array. It's useful, innocent, and seemingly harmless.
Here is the logic they used to secure it:
public function map(?iterable $array, string|callable|\Closure $function): ?array
{
// ... [snip] ...
// The Fatal Flaw
if (\is_string($function) && !\in_array($function, $this->allowedPHPFunctions, true)) {
throw AdapterException::securityFunctionNotAllowed($function);
}
return array_map($function, $array);
}Do you see it? Read that if statement again. The security check only runs if \is_string($function) is true. The developer assumed that if you are calling a function by name, you are providing a string (e.g., 'strtoupper').
But PHP supports array callables. You can call a static method of a class by passing an array like ['ClassName', 'MethodName']. Since an array is not a string, \is_string($function) returns false. The entire security block is skipped, and the code proceeds directly to array_map, executing whatever method the attacker provided. It's like locking the front door but leaving the entire back wall missing.
The Exploit: Crafting the Bypass
To exploit this, an attacker needs control over a Twig template. This isn't uncommon in Shopware ecosystems; you might find it in CMS blocks, email templates, or theme configurations. Once we have a compilation context, we need to pass an array callable to the map filter.
Standard usage looks like this:
{{ ['hello']|map('strtoupper') }}The security check sees 'strtoupper' is a string, checks the list, finds it (likely allowed), and runs it.
Malicious usage looks like this:
{{ ['id']|map(['Shopware\\Core\\Framework\\Adapter\\Cache\\CacheIdLoader', 'write']) }}In this scenario, ['Shopware...Loader', 'write'] is passed as $function. The SecurityExtension checks: "Is this a string?" No, it's an array. "Okay, go right ahead." The array_map function then executes Shopware\Core\Framework\Adapter\Cache\CacheIdLoader::write('id').
While CacheIdLoader::write is just an example, the ability to call any public static method (or instance method if you can get an object reference) means RCE is usually trivial. You simply look for a "gadget"—a method that interacts with the file system, executes commands, or manipulates internal state in a dangerous way.
The Code: Before and After
The fix provided by the Shopware team is elegantly simple: enforce normalization. If the function is an array, squash it into a string format (Class::Method) before running the check. This forces the array callable to undergo the same scrutiny as a string callable.
Here is the diff from commit 3966b05590e29432b8485ba47b4fcd14dd0b8475:
public function map(?iterable $array, string|callable|\Closure $function): ?array
{
if ($array === null) {
return null;
}
+ if (\is_array($function)) {
+ $function = implode('::', $function);
+ \assert(\is_callable($function));
+ }
if (\is_string($function) && !\in_array($function, $this->allowedPHPFunctions, true)) {
throw AdapterException::securityFunctionNotAllowed($function);
}By adding that small block, they ensure that ['System', 'exec'] becomes 'System::exec'. The subsequent is_string check (or the logic following it) will now catch it, and since 'System::exec' is definitely not in the allowlist, the exploit is killed.
The Impact: Why Severity is 9.8
This is a textbook Critical vulnerability. The requirements for exploitation are low (template access), and the impact is total compromise (RCE). In e-commerce, the stakes are incredibly high. We aren't just talking about defacing a website; we are talking about full database access, credit card skimming (if not tokenized properly), and customer data exfiltration.
Because Shopware handles sensitive customer PII and often sits within a network that accesses payment gateways or ERP systems, an RCE here is a golden ticket for ransomware groups. The vulnerability does not require authentication to the server shell, only access to the application layer where templates are processed. In many setups, marketing teams or lower-privileged admins have access to CMS features, making this a prime candidate for privilege escalation.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Shopware 6 Shopware | < 6.6.x (Patched Jan 2026) | Post-Jan-5-2026 Release |
| Attribute | Detail |
|---|---|
| Attack Vector | Network (Twig Template Injection) |
| CVSS v3.1 | 9.8 (Critical) |
| CWE ID | CWE-843 |
| CWE Name | Access of Resource Using Incompatible Type ('Type Confusion') |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Proof of Concept (PoC) |
MITRE ATT&CK Mapping
The program allocates or initializes a resource using one type, but it later accesses that resource using a type that is incompatible with the original type.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.