# Yelp for Business: locked Email field silently editable via API

Yelp Biz bio endpoint mass-assigns email despite locked UI, letting a session token rebind the recovery email and chain to full account takeover.

- Payout: hidden
- Severity: medium
- Program: Yelp
- Found by: 0xmanticore
- Disclosed: 2026-07-02
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/3766455
- Library page: https://bugbountylibrary.com/r/h1%3A3766455
- Tags: area:web3 domain:web impact:ato kind:bounty platform:hackerone sev:medium tech:graphql tech:rest technique:chained vuln:access-control vuln:account-takeover

**Target:** REST endpoint POST /account/info/bio/v1 and GraphQL GetAccountInfo
**Root cause:** The bio update endpoint accepts and persists an `email` field without re-authentication or confirmation, while the UI treats email as read-only and the password endpoint enforces old_password.
**Steps:**
1. Obtain victim x-auth-token; baseline email via GraphQL GetAccountInfo on graphql-mobile-api.yelp.com
2. POST https://biz-app.yelp.com/account/info/bio/v1 with `{"first_name":...,"last_name":...,"email":"attacker@example.com","role":"OWNER"}` and x-auth-token
3. Re-run GetAccountInfo to confirm private.email persisted to attacker address
4. POST https://biz-app.yelp.com/account/forgot_password/v1 with `email=attacker%40example.com`
5. Receive reset link at attacker mailbox, set new password, login via /account/login/v1
**How found:** Noticed the UI renders email read-only with a lock icon but the Save call posts a full object to the bio endpoint; tested whether the server honored an injected email field, then cross-checked persistence on a separate GraphQL backend.
**Exploitation:** Attacker with a brief session token rewrites the account email via the bio endpoint, triggers forgot_password to the attacker mailbox, receives the reset token link, sets a new password, and logs in for permanent ownership.
**Escalation:** Chains email rebind to password reset to convert ephemeral token access into full credentialed ATO.
**Insight:** When a UI marks a field read-only, replay the underlying save request with that field injected; verify persistence on an independent backend (GraphQL vs REST) to rule out echo, and check whether the field is a recovery anchor for a reset chain.
