// HackTricks · Web Pentesting

Reverse Tab Nabbing

Reverse Tab Nabbing

Description

If an attacker controls the href of a link opened with target="_blank" and the new page receives an opener reference, the attacker-controlled page can navigate the original tab through window.opener. Explicit rel="opener" requests that relationship. Current HTML behavior gives _blank links implicit noopener in modern browsers, but older browsers, embedded webviews, window.open() calls, or explicit opener can still expose it.[1][3]

A regular way to abuse this behaviour would be to change the location of the original web via window.opener.location = https://attacker.com/victim.html to a web controlled by the attacker that looks like the original one, so it can imitate the login form of the original website and ask for credentials to the user.

Cross-origin policy does not give the malicious page arbitrary DOM or JavaScript access to the opener. It exposes only the limited cross-origin WindowProxy surface, whose most important tabnabbing capability is navigation of the opener.[3]

Overview

Link between parent and child pages when prevention attribute is not used:

https://owasp.org/www-community/assets/images/TABNABBING_OVERVIEW_WITH_LINK.png

Link between parent and child pages when prevention attribute is used:

https://owasp.org/www-community/assets/images/TABNABBING_OVERVIEW_WITHOUT_LINK.png

Examples

Create the following pages in a folder and run a web server with python3 -m http.server
Then access http://127.0.0.1:8000/vulnerable.html, click the link, and observe that the original tab’s URL changes.

<!DOCTYPE html>
<html>
<body>
<h1>Victim Site</h1>
<a href="http://127.0.0.1:8000/malicious.html" target="_blank" rel="opener">Controlled by the attacker</a>
</body>
</html>
<!DOCTYPE html>
<html>
 <body>
  <script>
  window.opener.location = "http://127.0.0.1:8000/malicious_redir.html";
  </script>
 </body>
</html>
<!DOCTYPE html>
<html>
<body>
<h1>New Malicious Site</h1>
</body>
</html>

Accessible properties

In the scenario where a cross-origin access occurs (access across different domains), the properties of the window JavaScript class instance, referred to by the opener JavaScript object reference, that can be accessed by a malicious site are limited to the following:

  • opener.closed: This property is accessed to determine if a window has been closed, returning a boolean value.
  • opener.frames: Returns a window proxy for the opener’s frame hierarchy; it does not expose cross-origin iframe DOM elements.
  • opener.length: Returns the number of child browsing contexts (frames).
  • opener.opener: A reference to the window that opened the current window can be obtained through this property.
  • opener.parent: This property returns the parent window of the current window.
  • opener.self: Access to the current window itself is provided by this property.
  • opener.top: This property returns the topmost browser window.

When the documents are same-origin, normal same-origin Window access applies.[3]

Prevention

Use rel="noopener" (and noreferrer when referrer suppression is also desired), avoid explicit opener, and null the opener for script-created windows where appropriate. OWASP’s HTML5 cheat sheet documents these defenses.[2]

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.