# Large-site Quick Edit reparses a Contributor display name into active HTML

Contributor stores entity-encoded HTML in display name; on >10k-user sites Quick Edit decodes it via .text() into .prepend(), yielding stored DOM XSS and admin account creation.

- Payout: not disclosed
- Severity: medium
- Program: WordPress
- Credited: mappymappy123
- Disclosed: 2026-10-21
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/3878399
- Library page: https://bugbountylibrary.com/r/h1%3A3878399
- Tags: domain:web impact:ato impact:privesc kind:bounty platform:hackerone sev:medium tech:php technique:source-review vuln:access-control vuln:misconfiguration vuln:xss

**Target:** wp-admin/js/inline-edit-post.js Quick Edit author fallback
**Root cause:** WordPress preserves valid entities like &lt; in display names; in large-site mode Quick Edit omits author options and the fallback reads the author cell via .text() then concatenates into HTML passed to .prepend(), decoding the entity into a live element.
**Steps:**
1. Deploy WordPress 7.0.2 with user_count option set to 10001 to force wp_is_large_user_count() true.
2. As Contributor, POST to `/index.php?rest_route=/wp/v2/users/{id}` with `name: "&lt;img src=x onerror=eval(atob(location.hash.slice(1)))&gt;"`.
3. Create a post and Submit for Review.
4. Send admin URL `edit.php?post_status=pending&post_type=post#<base64 payload>` where fragment holds the JS payload.
5. Admin opens URL and clicks Quick Edit on the pending post; `.text()` decodes entity and `.prepend()` parses `<img onerror>` executing payload.
6. Second-stage payload fetches `/wp-admin/user-new.php`, extracts `_wpnonce_create-user`, POSTs createuser with role=administrator.
**How found:** Researcher noticed large-site mode omits the author dropdown in Quick Edit, then read inline-edit-post.js and saw the fallback uses `.text()` on the author cell and concatenates into HTML for `.prepend()`, suspecting entity decoding would reparse stored markup.
**Exploitation:** Attacker needs Contributor account and a pending post. They store entity-encoded `<img onerror>` in display name, lure admin to a crafted Posts-page URL with base64 payload in fragment, and rely on admin clicking Quick Edit. The onerror reads location.hash, decodes and evals it, then uses same-origin fetch to grab the create-user nonce and POST a new administrator account.
**Escalation:** Chained stored DOM XSS with nonce theft to create a persistent Administrator account, achieving full site takeover.
**Insight:** When a UI branch is only taken under scale conditions (large user counts, pagination fallbacks), audit the alternate code path separately: it often skips escaping that the normal path applies. Look for `.text()` output concatenated into HTML sinks like `.prepend()`/`.html()`.
