CVE-2026-23498

Shopware 6: Mapping Your Way to RCE via Twig Type Juggling

Alon Barad
Alon Barad
Software Engineer

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.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Affected Systems

Shopware 6 CoreShopware 6 ProfessionalShopware 6 Enterprise

Affected Versions Detail

Product
Affected Versions
Fixed Version
Shopware 6
Shopware
< 6.6.x (Patched Jan 2026)Post-Jan-5-2026 Release
AttributeDetail
Attack VectorNetwork (Twig Template Injection)
CVSS v3.19.8 (Critical)
CWE IDCWE-843
CWE NameAccess of Resource Using Incompatible Type ('Type Confusion')
ImpactRemote Code Execution (RCE)
Exploit StatusProof of Concept (PoC)
CWE-843
Access of Resource Using Incompatible Type ('Type Confusion')

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.

Vulnerability Timeline

Fix committed by Jonas Elfering
2026-01-05
Vulnerability tracked in security feeds
2026-01-13
Deep dive analysis completed
2026-01-14

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.