
Docker lab reproducing the complete CVE-2026-75650 StyleSmuggler unauthenticated HTTP RCE and validating Adobe VULN-39341.
This standalone lab reproduces the complete unauthenticated StyleSmuggler HTTP chain on a revision-pinned Magento Open Source 2.4.9 checkout. It then applies Adobe's VULN-39341 patch and proves that the identical request sequence no longer reaches PHP execution.
The proof is deliberately marker-only. The poisoned report evaluates one fixed
hash('sha256', fresh_nonce) expression. The expected digest is never sent to
Magento, so seeing it in the Stage 2 response independently proves that PHP
parsed and executed the report. The PoC has no command runner, downloader,
callback, proxy, or operator-supplied PHP.
The lab provides:
755e34dd689021c5165db9d35ecff74f7dc51527;This is a lab-assisted proof for the pinned build. It is not a scanner result or an assumption based on a report ID, HTTP status, failed email, or file upload. The fresh response marker is the execution oracle.
Use only this disposable lab. The HTTP PoC refuses every non-loopback target and accepts no arbitrary payload or report path. Do not place the probes under a production web root. For an existing installation, use the read-only validator or a disposable staging clone.
Requirements:
Only nginx is published, and only on 127.0.0.1:8096 by default. MariaDB,
Redis, OpenSearch, PHP-FPM, and the secondary report gateway are not exposed to
the host network. The public gateway selects production-style Magento error
handling so Stage 1 creates a normal var/report record.
cd docker-lab
cp .env.example .env
docker compose up -d --build
docker compose logs -f php
The initial Magento clone, dependency install, and application install normally
take 15–40 minutes. When the PHP log prints Ready, run:
make ab
The A/B command always attempts to leave the source patched and removes the exact report artifacts disclosed during its HTTP runs.
The test passes only when all three proof layers match their controls:
Unpatched report: raw-tag=true, guard=false
Patched report: raw-tag=false, guard=true, neutralized=true
Unpatched component: marker=true
Patched component: marker=false
Full HTTP, vulnerable: execution_observed=true, expectation_met=true
Full HTTP, patched: execution_observed=false, expectation_met=true
[PASS] Report storage, component execution, and full HTTP execution match all A/B controls.
poc/http_rce_probe.py sends only unauthenticated requests to the loopback
Magento gateway:
poison a normal Magento failure report
-> createEmptyCart
-> setGuestEmailOnCart
-> setBillingAddressOnCart with the recursive formatter construction
-> handlePayflowProResponse with a declined response
-> billing-address formatter signs an unresolved Preview block
-> failed-payment email filter constructs Email Template Preview
-> Preview reads request query parameters type/text/styles
-> ColumnSet -> UrlGeneratorFactory -> Aws S3Client
-> with_resolved callback -> ArrayScanner::collectEntities()
-> include the poisoned report
-> fresh SHA-256 response marker
The important connector is the billing address. A self-referencing postcode, the stock HTML address format, and nested template directives cause Magento's basic formatter to wrap the unresolved Preview block in Magento's own deferred directive signature. The later failed-payment email filter shares that signature provider and accepts the block. Preview is then constructed directly by Layout inside the GraphQL request; no admin route is dispatched.
make http-vulnerable # requires execution_observed=true
make http-patched # requires execution_observed=false
make component-ab # decomposed report/component controls only
The HTTP wrappers select the requested source state, run the loopback-only probe, and remove the exact report ID returned by Magento. To inspect the raw probe directly in the disposable lab:
make vulnerable
python3 poc/http_rce_probe.py \
--target http://127.0.0.1:8096 \
--expect vulnerable
make patched
python3 poc/http_rce_probe.py \
--target http://127.0.0.1:8096 \
--expect patched
Direct invocation leaves the generated report for inspection; the
make http-* wrappers perform cleanup.
The direct component probe remains available because it localizes failures in the downstream gadget independently from the recursive address-formatting connector.
This check never starts Magento or executes code from the mounted tree. The container has no network, no Linux capabilities, a read-only root filesystem, and a read-only target mount.
make validate TARGET=/absolute/path/to/magento
Expected fully patched verdict:
Summary: 9/9 controls present
Verdict: FULL_CONTROL_SET_PRESENT
Anything less is reported as FULL_CONTROL_SET_NOT_CONFIRMED, not automatically
as exploitable. Confirm the exact Commerce edition/version and apply Adobe's
version-matched patch through its supported deployment process.
Use the official bulletin and the patch matching the deployed release:
The patch embedded here is path-mapped only for the public 2.4.9 monorepo lab. Do not apply it directly to a production Composer installation. Patching also does not remove an existing implant or restore exposed credentials.
The missing HTTP connector was published and independently demonstrated by Fortbridge on 12 September 2026:
This lab uses an independently implemented, loopback-only marker probe and retains its own pinned Magento and Adobe-patch A/B controls.
See Technical notes for the data flow, patch controls, and retrospective. Exact local test results are recorded in VALIDATION.md.
make down # preserve volumes
make reset # delete this lab's source, database, and OpenSearch volumes
Lab-authored material is MIT licensed. See LICENSE and NOTICE.md.