










Senior Security Researcher, Fastly

Overview
Sansec discovered the vulnerability in Adobe Commerce & Magento and found it being exploited in the wild prior to patches being released, as early as September 4th, 2026
Adobe published APSB26-146 on September 7th, 2026 to address portions of the vulnerability chain
Fastly has released a virtual patch for CVE-2026-75650 that customers can enable to block requests targeting the vulnerability
CVE-2026-75650 is an unauthenticated Server-Side Template Injection (SSTI) vulnerability that leads directly to remote code execution (RCE). Exploiting the vulnerability occurs in three stages across four requests:
Poisoning logs with PHP code
Setting up the cart and inserting the SSTI payload into a cart’s address details
Executing the SSTI and causing RCE via a failed payment email
The first stage is all about getting unsanitized PHP code onto the target’s disk. We reproduced one method in our labs, while at least one more confirmed method has been observed in the wild. It’s important to note that there are likely many more ways to accomplish this stage. The variant we reproduced locally logs an unescaped error message from an invalid store code, preserving the malicious PHP embedded in the request. A request would look something like this:
POST /graphql HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Store: <?=eval(base64_decode('{{payload_b64}}'));?>
{"query":"{ storeConfig { store_code } }"}The Store header is where the payload is embedded, and in the wild will likely include encoded content for evasion purposes. When Magento logs this error, the <?= is logged unescaped, which is important when we get to stage 3. The actual PHP code will vary during exploitation.
The second request sets up a guest cart, and the third request containing the SSTI payload is within the cart’s address. There’s nothing interesting about the second request, so it is omitted for brevity.
Understanding the SSTI payload requires understanding parts of Magento’s templating engine. Magento’s templating engine performs variable substitution using “directives” (e.g., var, block), which are identified via different regexes for each directive. A “filter” is responsible for processing directives and does so in two passes. In the first pass, a filter resolves any directives it can, and in the second pass it processes “deferred” directives that have been signed by a nested filter. When a directive is processed, has a parent filter, and its output is the same as the directive itself, it gets “signed” (i.e., *Sig*{{directive}}*Sig*) for the parent filter to resolve.
This is where the trouble begins. When a directive is signed, a str_replace is used on the output to replace the original directive with the signed one. However, the output includes all previously processed directives, not just the current one. So let’s dive into how Style Smuggler utilizes these directives.
Style Smuggler utilizes a failed payment email template that parses the address fields with a nested filter. The filter processing the address cannot process block directives. So, we send the following in the address fields of the third request:
street: {{if postcode}}{{var postcode}}{{/if}}{{/var}}{{if postcode}}{{var postcode}}{{/if}}{{if city}}{{block class=Magento\\Email\\Block\\Adminhtml\\Template\\Preview}}{{/if}}
postcode: {{var postcode}}
When the postcode is processed by the address filter, it matches the original directive content, and is signed. However, due to the str_replace happening on the entire output, the signatures also get placed where we inserted {{var postcode}} in the street portion of the address. Due to some confusion in the directive-identifying regex, the final “processed” address is passed up to the email filter containing {{var postcode}}{{var postcode}}Sig{{blockclass=Magento\\Email\\Block\\Adminhtml\\Template\\Preview}}Sig<br /> The email filter then resolves the signed block directive we smuggled in our street portion of the address. Prior to Adobe’s patch, block directives instantiate the class provided regardless of whether it is a “Block” class or not, so we now have arbitrary object creation.
At this point we have 1) PHP code in a log file and 2) an SSTI capable of instantiating arbitrary classes. Connecting the two requires instantiating a class that can execute the code we dropped into the log file in Stage 1. This is where the inserted Magento\Email\Block\Adminhtml\Template\Preview becomes important. It’s a class that can render templates from the “text”, “styles”, and “type” query parameters. So we’ll send a request with the following query parameters (URL encoded):
text: {{block class=Magento\Backend\Block\Widget\Grid\ColumnSet rowUrl=$this.template_styles cache_key=y}}
styles (formatted as a json-encoded query parameter):
{
"first": "../var/log/system.log",
"generatorClass": "Aws\\S3\\S3Client",
"region": "us-east-1",
"version": "latest",
"s3_us_east_1_regional_endpoint": "regional",
"with_resolved": [
{
"instance": "Magento\\Setup\\Module\\Di\\Code\\Scanner\\ArrayScanner"
},
"collectEntities"
],
"path": "dummy"
}type: 2
In this request, we also simultaneously use the graphql handlePayflowProResponse mutation to kick off the failed payment template processing. This sets off the following chain of events:
Our previously inserted “Preview” block is processed during the email template processing, causing our other block payload “ColumnSet” to be created from the “text” query parameter.
ColumnSet receives the “styles” array as args, and calls a “UrlGeneratorFactory” with our provided generatorClass “Aws\S3\S3Client.”
During the construction of the S3Client, our provided “Magento\Setup\Module\Di\Code\Scanner\ArrayScanner” is created and calls the “collectEntities” method
collectEntities calls “include” on the “first” value we provided within the styles array, which is our poisoned log file.
The code we placed in the log file is executed during the include (due to the unescaped <?=), and now we have RCE.
This is where “Style Smuggler” gets its name. It utilizes the “styles” parameter used by the Preview class to smuggle the extra gadget information needed to cause code execution. It is also important to note that it is possible that other usable gadgets exist within the Magento codebase and we have identified at least one variant using a different payload besides the “Preview” block.
To help the community test for instances vulnerable to Style Smuggler, we have submitted a Nuclei template for CVE-2026-75650 to the official nuclei-templates repository. While we developed the template ourselves, we waited to submit it until a breakdown of the vulnerability was posted by Graycore and acknowledged by Sansec to avoid disclosing previously withheld information about the vulnerability. We tested the template against Magento v2.4.9 and v2.4.7-p2 locally, to ensure it would work correctly across multiple versions.
Fastly has released a virtual patch for CVE-2026-75650 that covers each stage of the Style Smuggler exploit. This provides defense in depth against variants within one part of the chain and gives operators time to patch their systems. This virtual patch is resilient to false positives for normal operations, including admin template updates, and is unrelated to the false positives described in other articles.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。