Someone left the warehouse loading dock wide open and attackers are already driving trucks through it
CVE-2026-48356, dubbed PolyShell by Sansec, is an unrestricted file upload vulnerability in Magento's guest-cart REST API (POST /V1/guest-carts/{cartId}/items). The flaw exists because the ImageContentValidator only checks image headers via getimagesizefromstring() — never the file extension — and ImageProcessor::getFileName preserves the caller-supplied extension. An attacker sends a single unauthenticated POST containing a polyglot file (GIF89a magic bytes prepended to PHP code, saved as .php), which lands in the web-accessible pub/media/custom_options/quote/ directory and executes as the Magento web user. Every version of Adobe Commerce from 2.4.4 through 2.4.9 (all patch levels), Magento Open Source 2.4.6–2.4.9, and Commerce B2B 1.3.3–1.5.3 are affected. Sansec reports the underlying flaw has existed since the first Magento 2 release.
Adobe rates this CRITICAL at 9.3 (CVSS:3.1 with UI:R), but the vendor vector understates reality. The actual REST API attack path documented by Sansec and reproduced by Abraxas Labs requires zero user interaction — the attacker sends a POST and directly accesses the uploaded shell. With UI:N, the recalculated CVSS exceeds 9.8. More importantly, this is not theoretical: as of March 2026, Sansec Shield recorded exploitation attempts against 79.5% of monitored Magento stores, and a campaign tied to deepveindrilling[.]com has compromised 953+ online stores with JavaScript skimmers and accesson.php backdoors. Active exploitation evidence and a public weaponized PoC mean the noisgate mitigation SLA is overridden: patch or mitigate immediately, within hours. The noisgate remediation SLA for CRITICAL is ≤90 days, but given active exploitation, you should treat the vendor patch as an emergency deployment — not a quarterly cycle item.
5 steps from start to impact.
Enumerate target Magento instance
X-Magento-*), and characteristic URL patterns (/rest/V1/, /pub/static/) make fingerprinting trivial. Shodan and FOFA index approximately 50,000 exposed Magento instances.- Internet-facing Magento/Adobe Commerce instance running any version ≤2.4.9 (unpatched)
- None — Magento stores are internet-facing by design and easily fingerprinted
http.component:Magento; Nuclei template magento-detectCreate a guest cart via REST API
POST /rest/default/V1/guest-carts which returns a cartId string. This is a legitimate, unauthenticated, public API endpoint designed for guest checkout. No credentials, tokens, or session cookies are required.- Guest checkout REST API is enabled (default in all Magento installations)
- Virtually none — disabling the guest-cart API breaks guest checkout, a core business function
Upload polyglot PHP shell via cart item
POST /rest/default/V1/guest-carts/{cartId}/items with a JSON body containing a custom_options[].file_info object. The base64-encoded payload starts with GIF89a magic bytes (passing getimagesizefromstring()), followed by PHP code (<?php system($_GET['cmd']); ?>). The filename ends in .php. Three missing validation checks — no option ID validation, no option type gating, no extension restriction — allow the file to be written verbatim to pub/media/custom_options/quote/. The API response JSON includes the exact filename. Weaponized tool: abraxas/CVE-2026-48356 (Python).- Target has not applied APSB26-73 patch
pub/media/directory is web-accessible (default Magento layout)
- A WAF inspecting REST API bodies for polyglot signatures could block this, but most WAF rulesets do not inspect base64-decoded content inside JSON payloads by default
GIF89a followed by <?php in base64-decoded POST bodies; file integrity monitoring on pub/media/custom_options/Execute uploaded webshell
GET /media/custom_options/quote/{filename}.php?cmd=id and receives command output as the web server user (typically www-data or apache). The Magento web user typically has read access to app/etc/env.php, which contains database credentials, encryption keys, and Redis/RabbitMQ secrets.- PHP execution is enabled in the
pub/media/directory (default Apache/Nginx Magento configs)
- Disabling PHP execution in
pub/media/via.htaccessor Nginx location blocks would break this step
.php requests under /media/custom_options/; EDR/HIDS detecting new PHP files in media directoriesEstablish persistence and exfiltrate data
accesson.php) across multiple directories and inject JavaScript skimmers into CMS blocks to exfiltrate payment card data from checkout pages. Database credentials from env.php provide direct access to customer PII, order history, and stored payment tokens. The deepveindrilling[.]com campaign demonstrates the full chain: polyglot upload → backdoor → JavaScript loader → card skimmer across 953+ stores.- Initial webshell execution (Step 4)
- Content Security Policy headers may block inline script injection on some stores, but CSP adoption in Magento stores is low
accesson.php and known skimmer patterns; CSP violation reportsphp_flag engine off in .htaccess or location ~ \.php$ { deny all; } in the Nginx config for the pub/media/ directory tree. This breaks the execution step of the chain — uploaded shells become inert text files. This is a zero-downtime change that does not affect Magento functionality since pub/media/ should only serve static assets. Given active exploitation, deploy within hours, not days.POST /rest/*/V1/guest-carts/*/items requests, base64-decodes the file_info content, and blocks payloads containing PHP tags (<?php, <?=) or executable extensions (.php, .phtml, .phar). This stops the upload step entirely. If your WAF cannot inspect base64-decoded JSON bodies, block the file_info parameter entirely on guest-cart endpoints. Deploy within hours given active exploitation.2.4.x-2026-jul security update for your version branch. This is the definitive fix — it adds option ID validation, option type gating, and file extension restrictions to the ImageContentValidator. Given active exploitation, treat this as an emergency patch — deploy within hours to days, not the standard noisgate CRITICAL remediation SLA of ≤90 days. Test in staging first but do not let QA cycles extend beyond 48 hours..php files in pub/media/custom_options/quote/ and across all pub/media/ subdirectories. Look for accesson.php backdoors, files with GIF89a headers containing PHP code, and JavaScript injections in CMS blocks referencing deepveindrilling[.]com or similar exfiltration domains. Run Sansec eComscan or grep -rn '<?php' pub/media/ as an immediate triage step. If indicators are found, assume full store compromise including database credentials in app/etc/env.php.- Rate limiting on the REST API — the entire attack is a single POST request followed by a single GET; rate limits have no effect on a one-shot exploit.
- CAPTCHA or bot protection on checkout flows — the vulnerable endpoint is the guest-cart item addition API, which fires before any checkout CAPTCHA is presented.
- Standard antivirus/malware scanning — polyglot files pass AV signature checks because they have valid GIF89a image headers; the PHP payload is only triggered by the web server's PHP interpreter.
- Content-Type validation at the HTTP level — the upload is embedded as base64 inside a JSON body with
Content-Type: application/json; the dangerous file type is only materialized server-side after decoding. - Magento Admin 2FA/MFA — the attack never touches the admin panel; it uses the unauthenticated storefront REST API.
The supporting signals.
| In-the-wild exploitation | Active and widespread. Sansec Shield documented exploitation attempts against 79.5% of monitored stores by March 30, 2026. A campaign linked to deepveindrilling[.]com compromised 953+ stores with JS skimmers (SecurityWeek). |
|---|---|
| Proof-of-concept | Public weaponized PoC. Python exploit by Abraxas Labs: abraxas/CVE-2026-48356, published September 26, 2026. Targets POST /V1/guest-carts/{cartId}/items with polyglot GIF89a+PHP payload. Also indexed on Sploitus. |
| EPSS | 17.9% probability (96.8th percentile) per current Feedly intelligence — significantly elevated from the initial 1.0% at disclosure, reflecting confirmed mass exploitation. |
| CISA KEV | Not listed as of October 27, 2026. Meets all KEV inclusion criteria (active exploitation, vendor patch available, assigned CVE). Inclusion likely imminent given the 953-store campaign evidence. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N (vendor, 9.3). Disputed: the REST API attack path requires no user interaction (UI:N). With UI:N, the calculated score exceeds 9.8. Adobe's APSB26-73 bulletin separately lists this as 9.6. |
| Affected versions | Adobe Commerce 2.4.4–2.4.9 (all patch levels through -p18/-p17/-p15/-p10/-p5), Magento Open Source 2.4.6–2.4.9 (all patch levels), Commerce B2B 1.3.3–1.5.3, Adobe I/O Events for Commerce. Sansec states vulnerable code exists since the first Magento 2 release. |
| Fixed versions | APSB26-73 (July 14, 2026): 2.4.9-2026-jul, 2.4.8-2026-jul, 2.4.7-2026-jul, 2.4.6-2026-jul, 2.4.5-2026-jul, 2.4.4-2026-jul. The fix was first included in the 2.4.9-alpha3 pre-release branch. |
| Internet exposure | ~50,000 Adobe Commerce/Magento instances indexed on Shodan and FOFA. These are e-commerce storefronts — internet-facing by definition. Every unpatched instance is directly exploitable. |
| Disclosure timeline | Sansec discovered and named PolyShell; exploitation observed in the wild from March 2026. Adobe published APSB26-73 on July 14, 2026. Abraxas Labs published the PoC on September 26, 2026. |
| Researcher credit | Sansec Forensics Team (discovery, naming as PolyShell, in-the-wild tracking). Abraxas Labs (@abraxas_null, abraxaslabs.tech) published the weaponized PoC. |
Sources.
- Sansec Research — PolyShell: unrestricted file upload in Magento
- Adobe Security Bulletin APSB26-73
- Abraxas Labs PoC — CVE-2026-48356 on GitHub
- SecurityWeek — 953 Online Stores Infected in Long-Running Campaign
- CIS Advisory — Multiple Vulnerabilities in Adobe Products
- SentinelOne Vulnerability Database — CVE-2026-48356
- Sploitus Exploit Entry — CVE-2026-48356 by Abraxas
- Feedly CVE Intelligence — CVE-2026-48356
Why this verdict
- Unauthenticated RCE with zero friction: The attack is a single POST to a default-enabled, public REST API endpoint. No authentication, no user interaction (despite the vendor CVSS claiming
UI:R), no race conditions, no memory corruption — just a JSON body with a polyglot file. Attack complexity is effectively zero. - Active mass exploitation at scale: This is not a theoretical risk. Sansec documented 79.5% of monitored stores being targeted, and 953+ stores have been confirmed compromised with payment card skimmers. The attack campaign predates the patch by months.
- Public weaponized PoC eliminates skill barrier: The Abraxas Labs Python PoC on GitHub automates the full chain from cart creation to shell upload. Any script kiddie with
curlcan reproduce this. - Role multiplier: Adobe Commerce and Magento are canonically internet-facing e-commerce platforms. 100% of installs occupy the high-value role by definition — they process payment card data (PCI DSS scope), store customer PII, and handle financial transactions. A successful exploit yields: (a) PCI cardholder data via database credentials in
env.php, (b) customer PII at scale, (c) persistent JavaScript skimmer injection for ongoing card theft, (d) supply-chain impact on downstream customers. Blast radius is tenant-to-fleet scale — a single exploit compromises the entire store's data and customer base. This is not a workstation vuln with hypothetical server deployment; the server IS the product. - Vendor CVSS understates the attack: Adobe's vector includes
UI:R, but the documented and observed attack path through the REST API requires no user interaction whatsoever. Correcting toUI:NwithS:Cpushes the base score to 10.0. Even conservatively, 9.8 is the floor.
Why not higher?
The reassessed score of 9.8 is already near the CVSS ceiling. A 10.0 would require Availability impact of High (A:H), which is not the primary impact here — the attack focuses on confidentiality (data theft) and integrity (webshell, skimmer injection) rather than denial of service. The scope-change designation and the absence of availability impact cap the theoretical maximum.
Why not lower?
Every downgrade factor fails to apply. The target is internet-facing by design (no network segmentation benefit). The API endpoint is enabled by default (disabling it breaks core checkout). No authentication is required. The PoC is public and weaponized. Mass exploitation is confirmed and ongoing. There is no deployment scenario where an Adobe Commerce instance is not a high-value target — it exists to process payments and hold customer data. The only friction point (WAF blocking polyglot payloads in base64-encoded JSON) requires non-default, highly specific rules that the vast majority of deployments lack.
Crowdsourced verification payload.
Run this script on the Magento application host as any user with read access to the Magento installation directory. Invoke with: bash check_polyshell.sh /var/www/magento (replace with your Magento root path). No elevated privileges required — read-only filesystem checks.
#!/usr/bin/env bash
# CVE-2026-48356 (PolyShell) Vulnerability Check
# Checks Adobe Commerce / Magento Open Source for APSB26-73 patch status
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -uo pipefail
MAGENTO_ROOT="${1:-}"
if [[ -z "$MAGENTO_ROOT" ]]; then
echo "[!] Usage: $0 /path/to/magento/root"
echo "Result: UNKNOWN"
exit 2
fi
if [[ ! -d "$MAGENTO_ROOT/vendor/magento" ]]; then
echo "[!] $MAGENTO_ROOT does not appear to be a Magento installation"
echo "Result: UNKNOWN"
exit 2
fi
# Detect version
VERSION=""
if [[ -f "$MAGENTO_ROOT/composer.lock" ]]; then
VERSION=$(python3 -c "
import json, sys
with open('$MAGENTO_ROOT/composer.lock') as f:
data = json.load(f)
for p in data.get('packages', []):
if p['name'] in ('magento/product-community-edition','magento/product-enterprise-edition'):
print(p['version']); sys.exit(0)
" 2>/dev/null || true)
fi
if [[ -z "$VERSION" ]] && command -v php &>/dev/null && [[ -f "$MAGENTO_ROOT/bin/magento" ]]; then
VERSION=$(php "$MAGENTO_ROOT/bin/magento" --version 2>/dev/null | grep -oP '[\d]+\.[\d]+\.[\d]+(-p\d+)?' || true)
fi
echo "[*] Detected version: ${VERSION:-unknown}"
# Check 1: Look for the APSB26-73 patch fix in ImageContentValidator
VALIDATOR="$MAGENTO_ROOT/vendor/magento/framework/Api/ImageContentValidator.php"
FIX_FOUND=false
if [[ -f "$VALIDATOR" ]]; then
# The patch adds explicit extension blocking for php/phtml/phar
if grep -qE 'php|phtml|phar' "$VALIDATOR" 2>/dev/null && grep -qE 'forbidden|blocklist|disallowed.*extension' "$VALIDATOR" 2>/dev/null; then
echo "[+] ImageContentValidator contains extension restriction (patch indicator)"
FIX_FOUND=true
else
echo "[-] ImageContentValidator lacks extension restriction"
fi
else
echo "[?] ImageContentValidator not found at expected path"
fi
# Check 2: Look for option type gating in quote item handling
PROCESSOR="$MAGENTO_ROOT/vendor/magento/module-quote/Model/Quote/Item/Processor.php"
if [[ -f "$PROCESSOR" ]]; then
if grep -qE 'validateOptionType|isFileOption|optionTypeCheck' "$PROCESSOR" 2>/dev/null; then
echo "[+] Quote Item Processor contains option type gating (patch indicator)"
FIX_FOUND=true
else
echo "[-] Quote Item Processor lacks option type gating"
fi
fi
# Check 3: Composer lock for July 2026 security release
if [[ -f "$MAGENTO_ROOT/composer.lock" ]]; then
if grep -q '2026-jul\|2026\.jul' "$MAGENTO_ROOT/composer.lock" 2>/dev/null; then
echo "[+] composer.lock references 2026-jul security release"
FIX_FOUND=true
fi
fi
# Check 4: Look for existing compromise indicators
echo ""
echo "[*] Scanning for compromise indicators..."
SHELLS_FOUND=0
if [[ -d "$MAGENTO_ROOT/pub/media/custom_options" ]]; then
SHELLS_FOUND=$(find "$MAGENTO_ROOT/pub/media/custom_options" -name '*.php' -o -name '*.phtml' -o -name '*.phar' 2>/dev/null | wc -l)
if [[ $SHELLS_FOUND -gt 0 ]]; then
echo "[!!] COMPROMISE INDICATOR: Found $SHELLS_FOUND PHP files in pub/media/custom_options/"
find "$MAGENTO_ROOT/pub/media/custom_options" -name '*.php' -o -name '*.phtml' -o -name '*.phar' 2>/dev/null
fi
fi
BACKDOOR=$(find "$MAGENTO_ROOT/pub" -name 'accesson.php' 2>/dev/null | head -5)
if [[ -n "$BACKDOOR" ]]; then
echo "[!!] COMPROMISE INDICATOR: accesson.php backdoor found:"
echo "$BACKDOOR"
fi
# Check 5: Is PHP execution disabled in pub/media? (partial mitigation)
HTACCESS="$MAGENTO_ROOT/pub/media/.htaccess"
PHP_DISABLED=false
if [[ -f "$HTACCESS" ]]; then
if grep -qi 'php_flag.*engine.*off\|RemoveHandler.*\.php\|deny from all' "$HTACCESS" 2>/dev/null; then
echo "[+] PHP execution appears disabled in pub/media/ (.htaccess)"
PHP_DISABLED=true
fi
fi
echo ""
# Final verdict
if $FIX_FOUND; then
echo "========================================"
echo "Result: PATCHED"
echo "========================================"
exit 0
else
echo "========================================"
if [[ $SHELLS_FOUND -gt 0 ]]; then
echo "[!!] ACTIVE COMPROMISE DETECTED"
fi
if $PHP_DISABLED; then
echo "[~] Partial mitigation in place (PHP disabled in media dir)"
fi
echo "Result: VULNERABLE"
echo "CVE-2026-48356 (PolyShell) - APSB26-73 patch NOT detected"
echo "========================================"
exit 1
fi