Feb 19, 2026·5 min read·11 visits
A type-juggling flaw in Shopware's Twig `map` filter allowed attackers to bypass the function allow-list by passing a PHP array callable instead of a string. This leads to RCE.
Shopware, a popular e-commerce platform, contained a critical Remote Code Execution (RCE) vulnerability in its Twig template security extension. The flaw arose from a regression in how the `map` filter validated PHP callbacks. While the developers diligently implemented an allow-list to block dangerous functions like `system` or `exec` when passed as strings, they failed to account for PHP's flexible type system, which allows callables to be passed as arrays (e.g., `[ClassName, MethodName]`). This oversight allowed attackers with template editing privileges to bypass the security check entirely, executing arbitrary code on the server.
Shopware is the backbone of thousands of European e-commerce stores. It handles credit cards, PII, and order history. To give frontend developers flexibility, Shopware exposes Twig, the standard PHP template engine. Twig is generally safe because it's sandboxed—you can't just write <?php system('rm -rf /'); ?> inside a template. It won't parse.
However, developers often need to transform data, so Shopware added the map filter. This acts just like PHP's native array_map, taking a list of items and a function to apply to them. Because map accepts a callback, it is inherently dangerous. If you let a user map system over an array of user input, you get RCE.
Shopware knew this. They wrote a SecurityExtension to act as a bouncer, checking every function passed to map against a strict allow-list. But as every hacker knows, bouncers can be distracted. In this case, the bouncer was only looking for people wearing nametags (strings), completely ignoring the group of people holding hands (arrays).
The root cause of CVE-2026-23498 is a classic case of "assuming the input type." In PHP, a callable—something you can execute—comes in many shapes. It can be a simple string (e.g., 'strtoupper'), an anonymous function (Closure), or an array representing a method call (e.g., ['MyClass', 'myMethod']).
The vulnerability lived in src/Core/Framework/Adapter/Twig/SecurityExtension.php. The developers wrote a check that looked essentially like this:
> "If the function name is a string, check if it is in the allowed list. If not, throw an error."
Do you see the logic gap? If the function is not a string—say, an array—the if block is skipped entirely. The code falls through to the return statement, which happily passes the unchecked array callable to array_map. PHP doesn't care that you skipped the security check; it just executes the method. This is like locking your front door but leaving the garage door wide open because "only cars go in there."
Let's look at the actual code diff. This is where the magic happens. The vulnerable code explicitly relied on is_string to trigger the security validation.
// VULNERABLE CODE (Pre-6.7.6.1)
public function map(?iterable $array, string|callable|\Closure $function): ?array
{
// ... (irrelevant code)
// THE BUG: strict check for string type
if (\is_string($function) && !\in_array($function, $this->allowedPHPFunctions, true)) {
throw AdapterException::securityFunctionNotAllowed($function);
}
// If $function was an array, we arrive here safely.
return array_map($function, $array);
}If an attacker passes ['SomeDangerousClass', 'pwn'], is_string returns false. The security exception is never thrown. array_map receives the array, sees it is a valid callable, and executes SomeDangerousClass::pwn().
The fix (Commit 3966b05) forces the input into a normalized string format before checking it:
// PATCHED CODE
if (\is_array($function)) {
// Normalize array callables to 'Class::Method' strings
$function = implode('::', $function);
\assert(\is_callable($function));
}
// Now the check runs regardless of input type
if (\is_string($function) && !\in_array($function, $this->allowedPHPFunctions, true)) {
throw AdapterException::securityFunctionNotAllowed($function);
}To exploit this, we need access to edit a Twig template. This is common in Shopware for administrators or developers who can modify email templates, CMS blocks, or document configurations. We need to find a static method in the codebase that allows command execution or file writing, or simply leverage a gadget chain.
While standard PHP functions like system are strings, PHP allows calling static methods via arrays. An attacker would hunt for a class available in the autoloaded scope that accepts a string argument and does something interesting.
Conceptual Attack Chain:
{{ customer.firstName }}, they inject:
{# Bypass the string check by using an array #}
{{ ['id'] | map(['\Shopware\Core\System\SystemConfig\SystemConfigService', 'set']) }}exec wrappers or file writers).array_map executes the static method. The server is compromised.Even without a direct exec gadget, simply calling internal Shopware methods that modify database state or configuration can lead to privilege escalation or persistent backdoors.
This is a Critical severity issue for a reason. RCE allows an attacker to completely takeover the host operating system. In the context of an e-commerce platform, this is catastrophic.
The only mitigating factor is the requirement for high privileges (access to Twig templates). However, in many organizations, "marketing" staff have access to CMS/Email templates but should not have the ability to execute code on the server.
The remediation is straightforward: Update to Shopware 6.7.6.1. The patch doesn't just block arrays; it normalizes them. By converting ['Class', 'Method'] into the string 'Class::Method', the code ensures that the allow-list logic is applied universally.
If you cannot patch immediately (why?), you can attempt to mitigate this by locking down the permissions of users who can edit templates. Ensure that no untrusted accounts have access to the Mail Template or CMS editors.
For security researchers and developers, the lesson is clear: Never rely on type checks for security boundaries in loosely typed languages. Always normalize input to a known state before validation.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Shopware Shopware | >= 6.7.0.0, < 6.7.6.1 | 6.7.6.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-863 (Incorrect Authorization) |
| Attack Vector | Network (Authenticated) |
| CVSS v3.1 | 7.2 (High) |
| EPSS Score | 0.06% |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | PoC Available |
The system performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. In this case, type juggling allows bypassing the check.