# XSS on any Shopify shop via abuse of the HTML5 structured clone algorithm in postMessage listener on "/:id/digital_wallets/dialog"

Shopify's digital_wallets dialog postMessage listener lacked origin checks and escaped payloads in-place, so a File object's read-only name property bypassed escaping to inject XSS on any shop.

- Payout: $3,000
- Severity: high
- Program: Shopify
- Credited: bored-engineer
- Disclosed: 2017-05-30
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/231053
- Library page: https://bugbountylibrary.com/r/h1%3A231053
- Tags: domain:web impact:ato kind:bounty platform:hackerone sev:high technique:chained technique:js-analysis technique:source-review vuln:xss

**Target:** /:id/digital_wallets/dialog postMessage listener
**Root cause:** The escapeHtml loop mutates the payload object in place using hasOwnProperty, so structured-cloneable objects like File with non-own read-only properties (name) skip escaping; the listener also never validates message origin.
**Steps:**
1. Load `/:id/digital_wallets/dialog` in an iframe on any shop.
2. postMessage `{type:'DigitalWalletsDialog:change', digitalWalletsDialog:true, payload:{title,button,lineItems:[new File([""],"<img src=xx: onerror=alert(document.domain)>")]}}` with targetOrigin `*`.
3. The `u` escape loop skips `File.name` because `hasOwnProperty('name')` is false.
4. The `m` template interpolates the unescaped name into innerHTML, firing the onerror payload.
**How found:** Read the minified listener, noticed escapeHtml overwrites properties in place rather than cloning, hypothesized objects with non-own properties would skip escaping, then tested structured-cloneable types and found File's read-only name fits the template.
**Exploitation:** Attacker page embeds the victim shop's dialog route in a hidden iframe, then postMessages a crafted payload containing a File object whose name holds an XSS payload; no user interaction required, executes in the shop origin.
**Escalation:** Injection is on the dialog route, not /admin, but the author notes escalating to /admin would be trivial from that origin.
**Insight:** When escaping mutates the input object in place via hasOwnProperty, try structured-cloneable objects (File, Blob, etc.) whose properties are on the prototype/read-only so they evade the loop while still being interpolated by the template.
