A GraphQL styles field smuggles PHP into Magento
On September 4, 2026, a Magento storefront on 2.4.6-p15, fully patched against every advisory Adobe had published to that date, took a POST to /graphql that carried a styles array on an unauthenticated query. The store returned a normal 200. Sansec worked backwards through the logs the next week and found the styles value was PHP, the store had written that PHP to a file it later included, and a Rust binary masquerading as [kworker/u:8:0] was already beaconing out over UDP 123. Three days after that first request Adobe shipped the emergency hotfix and named the bug CVE-2026-75650. The store had been running attacker code for the entire window between them.
The GraphQL styles field that ends in root
StyleSmuggler is a two request chain that turns a legitimate Magento template feature into an unauthenticated PHP loader. It scores CVSS 10.0 in APSB26-146 because it runs against the public front of the store with no session and lands as root-equivalent execution inside the PHP-FPM worker.
The first request delivers the payload. Magento's GraphQL layer accepts a styles array on several public queries, and older sanitization treats it as opaque CSS text destined for a rendered page. The value is not filtered as PHP and it is written to disk where PHP later reads. Sansec observed the payload landing in var/log/system.log via an invalid store code path, in var/report/ files, and inside customer custom option uploads. Arbitrary PHP source now sits inside a file Magento owns.
The second request detonates it. StyleSmuggler triggers the Payment Transaction Failed Reminder email, which walks the template engine through the {{block class="..."}} directive in Magento\Email\Model\Template\Filter::blockDirective(). As Graycore's stop-gap patch analysis makes explicit, that directive instantiates the requested class before it checks whether the class type is safe. Instantiating a scanner or grid factory class reaches Magento's dependency injection machinery, which resolves the poisoned file through PHP include or require_once. Rendering alone is the sink, the class the block loads is a valid Magento class, and the file it reads is the one the first request seeded.
styles field, a slot the request pipeline treats as inert style text. The template engine never parses it as code. Magento does the reading, on its own machinery, on the next matching event. Nothing in the path looks like an exploit until PHP is already running.
How a payment failed reminder becomes a PHP loader
Trace one live attack. A hostile client sends POST /graphql carrying styles: ["<?php system($_GET['c']); ?>"] and, in the same session, a header or store code the log stack writes verbatim. The response is normal, and var/log/system.log now contains a full PHP open tag and a shell primitive. The customer address on the record ends in .invalid, which is the tell Sansec's IOC toolkit uses to fingerprint the seed request. Minutes later the same client forces the reminder path, either at the job endpoint or through a payment transition, so blockDirective() builds a block whose factory routes through the DI scanner, which opens the seeded file with an include. That PHP open tag in the log becomes executing PHP as the PHP-FPM user, with reach to write to pub/media/, read app/etc/env.php, and touch the store's Redis and database sockets.
Rust implants land next. Sansec observed the first campaign drop ~/.local/share/.gvfsd/gvfsd-user on a five-minute cron, rotate by September 6 to fc-cache beaconing on UDP 123 to ntp.timesync.to as NTP-shaped traffic, and by September 7 to chronyd under /tmp/.chrony-<8hex>/, with in-memory updates so /proc/<pid>/exe stops matching the on-disk binary. A second operator arrives September 7 with a stateless PHP web shell at pub/media/catalog/product/cache/ss_<10hex>/sync_<10hex>.php and DNS exfil over oast.site. Both can sit on the same store at once, and several did.
127.0.0.1:6379. No outbound C2 is required for a Magento store to leak every active cart, admin session, and payment token in memory. A firewall that never fired is consistent with a store already emptied.
Why a store on the September 5 CVE feed still had shells for three days
Adobe reserved CVE-2026-75650 on September 5 and shipped APSB26-146 with the VULN-39341 composer patch on September 7, separately from the regular September release APSB26-138. A queue that sorts by CVSS put the finding at the top on September 8 when the KEV entry landed. A queue that sorts by exposure had it at the top on September 4, before there was a CVE to sort by, because a public /graphql on 2.4.6 through 2.4.9 was already vulnerable.
Two properties defeat a normal scanner. The bug is not a fingerprintable component version alone, so a store on 2.4.6-p15 current against every prior advisory still hits the block directive and DI scanner sink and reads clean on a pre-patch scan. The exploit primitive is a request pattern rather than a payload signature: the first request looks like an inert style setting, the second is an internal template render triggered by a real Magento job, and the only checks that catch it inspect GraphQL POST bodies for PHP open tags, which most storefronts do not run.
What TRIS puts on this StyleSmuggler finding
TRIS is the Threat and Risk Intelligence Scoring engine inside CVEasy AI. It scores a CVE against your inventory across a small number of layers, so the same CVE ranks differently on an exposed store and a sandboxed one. StyleSmuggler separates those layers cleanly.
Layer 1, exploitation status. Confirmed in the wild since September 4, 2026, three days before the vendor patch, with an active Rust implant, a second PHP web shell operator, and a CISA KEV entry dated September 8 carrying the September 11 federal remediation deadline. Top band. A proven PoC would not clear this bar; real telemetry from real stores does.
Layer 2, exposure. The bug needs only a reachable /graphql on 2.4.4 through 2.4.9 for Adobe Commerce, 2.4.6 through 2.4.9 for Magento Open Source, or 1.3.3 through 1.5.3 for B2B. TRIS asks whether the endpoint is reachable from the public internet or only from an authenticated CDN edge, and whether PHP execution is allowed under pub/media/ and pub/static/. An exposed storefront on a matching version is ACT band; a staging site behind a VPN on the same code is a full band lower.
Layer 3, blast radius. A Magento host carries the crypt key that decrypts saved payment data, admin session cookies for the back office, and outbound credentials to the payment processor and the tax service. TRIS treats stores that reach the processor from the same process as PHP-FPM as high blast radius, which pulls credential rotation into the plan by default.
Layer 4, patch friction. APSB26-146 is a composer patch on the current minor. Magento Open Source 2.4.5 and below have no official patch, which pushes those stores toward a minor upgrade or a WAF-plus-hardening posture. TRIS surfaces that friction so the queue reflects the actual work, rather than the CVSS in isolation.
Combining the four, StyleSmuggler is ACT band for an exposed store on an affected version and about two bands lower for a sandboxed lab. Same CVE, same NVD score, different priority once real exposure lands on it.
The patch, the hunt, and the rotation list
Patch. Apply APSB26-146 using the VULN-39341-composer-patches.zip bundle from repo.magento.com. Adobe Commerce and B2B ship the composer patch against 2.4.4 through 2.4.9 and 1.3.3 through 1.5.3. Magento Open Source has an official patch only on 2.4.6 through 2.4.9; stores on 2.4.5 or below need a minor upgrade first, or one of the community stop-gaps such as Graycore's block allowlist patch or brideo's layered mitigation package.
Interim mitigation. Rate limit and inspect POST bodies to /graphql for PHP open tags and suspicious styles arrays. Refuse PHP execution under pub/media/ and pub/static/ at the web server, which breaks the second operator's shell drop. Restrict the email template block directive through a community patch so it will not instantiate arbitrary classes.
Hunt. Grep var/log/system.log and everything under var/report/ for PHP open tags. Grep sales_order and customer records for addresses ending in .invalid. List any executable PHP under pub/media/ or pub/static/ and treat each hit as compromise until proven otherwise. Look for processes named [kworker/u:8:0], fc-cache, or chronyd that are not owned by root, and compare the SHA-256 of /proc/<pid>/exe against the on-disk binary since the implant does in-memory updates. On the network, alert on UDP 123 to ntp.timesync.to and DNS queries for oast.site.
Rotate. Reset the Magento crypt/key and re-encrypt saved payment records. Reset every admin password, invalidate every admin session, and audit admin_user for accounts you did not create. Rotate database credentials, payment processor API keys, tax service credentials, and any deployment tokens the PHP-FPM process could reach. Flush Redis and the database session store. If any hunt query fires, treat the checkout as compromised for the whole window from September 4 forward.
How CVEasy AI surfaces this
CVEasy AI is the number one local-first CTEM platform. It ingests the real sources for a finding like StyleSmuggler, the Sansec research post, the Adobe advisory, the KEV entry, and the community IOC toolkits, and runs them through TRIS against the Magento inventory on your surface. What comes back on your triage board is a concrete list of stores on an affected version with a reachable /graphql, ranked by whether they hold payment keys and whether PHP execution is allowed under their media paths, with the APSB26-146 patch path, the interim WAF rule, and the credential rotation list attached to each one. Because the whole pipeline runs on your hardware, the store code, the crypt keys, and the customer records under investigation never leave the building.