🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • CRLF / Header Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • APIs
  • Window References
  • Moving and Resizing
  • window.name ("target")
  • Hash fragments and IDs
  • Exploits
  • Holding Space
  • Keyboard Popunder
  1. Web
  2. Client-Side

Window Popup Tricks

Abusing browser functionality to do interesting things with popups and interactions

PreviousCross-Site Request Forgery (CSRF)NextCRLF / Header Injection

Last updated 3 months ago

APIs

Before trying to understand how we can abuse popup windows, we should understand the functions we can call from JavaScript. The main one is which opens a new window, either a tab or a popup. The destiction is made by the 3rd windowFeatures argument which is a string containing some key=value, options. Specifying the popup key here will force a popup, but specifying any position or size will do so as well:

window.open("https://example.com", "", "");  // Open new tab
window.open("https://example.com", "", "popup");  // Open popup
window.open("https://example.com", "", "width=200,height=200");  // Open small popup
window.open("https://example.com", "", "width=200,height=200,top=100,left=200"); // Open positioned popup

If you just add the above to a <script> tag without any extra code, you will get a warning like the following in most browsers by default:

<script>
  // Set event handler (`window.addEventListener("click", () => {})` also works)
  onclick = () => {
    window.open("https://example.com", "", "popup"); // Open popup
  }
</script>

This successfully triggers the popup. In Chromium, the onkeydown, onkeyup and onkeypress events also work, while on Firefox only onkeyup works. This popup will open the given URL in a top-level context, sending with it any SameSite=Lax cookies, making attacks that require cookies more often possible.

Tip: Headless browsers (--headless) lack this "popup blocker", so in automated environments you will often be able to start as many popups as you want, whenever you want.

Window References

We could also have saved the return value from window.open(), giving us a window reference. When the popup is cross-origin with the main page, we are very limited in what can be accessed on the window, but not fully out of options. See the following example:

<script>
  let w;
  onclick = () => {
    w = window.open("https://example.com", "", "popup"); // Open popup
    console.log(w);
    
    setTimeout(() => {
      w.location = "https://example.com/2"  // Change the URL of the popup
    }, 1000);
  }
</script>

Using the window reference, we can change the .location property to redirect the target page at any moment. For more complicated sequences, we could even redirect it to a URL same-origin with the main window, call some APIs that are only available for such windows, and then redirect it to the target page.

<script>
  onclick = async () => {
    // example.com will see `opener` as 'null' now, 
    // instead of a reference back to the main page
    window.open("https://example.com", '', 'popup,noopener')
  }
</script>

Moving and Resizing

let w;
onclick = () => {
  w = window.open(origin, "", "width=200,height=200,top=100,left=200");
  
  setTimeout(() => {
    // Move 100px to the right
    w.moveBy(100, 0);
    // Increase height by 200px
    w.resizeBy(0, 200);
  }, 1000);
}

Note that these methods are only available on same-origin popups, check the DevTools Console for errors if you try to change the URL from origin to some random other website. We can, however, move the window right at the start before the real page loads:

let w;
onclick = () => {
  w = window.open("https://example.com", "", "width=200,height=200,top=100,left=200");
  w.resizeBy(0, 200);  // Works
  
  setTimeout(() => {
    w.resizeBy(0, -200);  // Doesn't work
  }, 1000);
}

window.name ("target")

Below is an example:

let w;
let i = 0;
onclick = () => {
  switch (i++) {
    case 0:
      // 1st, open a new popup named "some-name"
      w = window.open("https://example.com", "some-name", "width=200,height=200,top=100,left=200");
      break;
    case 1:
      // 2nd, re-use the first popup to open this next page, gaining focus again
      w2 = window.open("https://example.com/2", "some-name");
      break;
  }
}

Tip: After the 2nd click, the popup window will be redirected to /2, reloading that page. If you just want to get another window reference and/or focus the popup window, you can open the location to the same URL with a # appended to it. Alternatively, you can also set it to an invalid URL like invalid://.

Either option will not reload the page, and only focus the window with that existing name this will be useful in the attacks described later.

By clicking twice on the main page, it first opens a popup with a specific name, and when this name is set the same for the second click, the same existing popup is used and only its location is changed. One scenario where this is useful is to put focus back on the main page by specifying it's window.name in a window.open() call from the popup itself:

<script>
  // Set main window's name to "main"
  window.name = "main";
  
  let w;
  onclick = () => {
    const blob = new Blob([`
        <script>
          onclick = () => {
            // Open main location again with a '#' appended, re-using the "main" name
            // Note that the same URL with a hash will not reload the page
            window.open("${location}#", "main")
          }
        <\/script>
      `], {
      type: "text/html",
    });
    
    // Create window to blob with HTML content
    w = window.open(URL.createObjectURL(blob), "some-name", "width=200,height=200,top=100,left=200");
  }
</script>

Clicking on the main page and then inside the popup will put focus back on the main window, while the popup remains in the background.

Hash fragments and IDs

URLs have a # hash fragment part that is sometimes accessed by JavaScript through location.hash, or used by the browser automatically to scroll to a certain element. When you click on the header above this paragraph, for example, #hash-fragments-and-ids is appended to the URL. When you copy and paste this URL into a new window, you will automatically scroll down to this header element.

This works because the header has an id="hash-fragments-and-ids" attribute which the browser looks for when you pass it as a hash fragment in the URL. Instead of manually typing a URL, other sites can also redirect or popup to a URL with a hash fragment.

Scrolling to a specific element is not the only this does, <input> or <button> elements will be automatically focused. See the following example:

Target (example.com)
<input id="some-button" type="submit" value="Submit">
Attack
<script>
  onclick = () => {
    // Automatically focus input on opening the popup
    window.open("https://example.com/#some-button", "", "popup")
  }
</script>

This can be automated by changing the .location attribute of a window reference. When you change the URL to the same path and query with a different hash fragment, it is not reloaded and will focus/scroll to the new element the hash points to. Here's an example that cycles through a few:

Target (example.com)
<button id="1">1</button>
<button id="2">2</button>
<button id="3">3</button>
Attack
<script>
  const target = "https://example.com";

  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  let w;
  onclick = async () => {
    w = window.open(target, "", "popup");
    // Cycle through buttons without reloading by changing the hash fragment only
    await sleep(1000);
    while (true) {
      w.location = target + "#1"
      await sleep(500);
      w.location = target + "#2"
      await sleep(500);
      w.location = target + "#3"
     await sleep(500);
    }
  }
</script>

Exploits

In this part, we learn how to abuse the above tricks in bigger exploit chains to make good proof of concepts that don't require much user interaction and are pretty convincing.

Holding Space

One powerful instruction to give a user is to hold space. As with holding any key, after a short second, the key will be repeatedly pressed while it is held. The same goes for the spacebar. The benefit of the spacebar is that it also serves as a way of pressing a button while it is focused. It allows for easy navigation without the mouse, but we can abuse it to perform unexpected interactions with a target page.

The idea is as follows:

  1. Instruct the user to hold space on our attacker's page, like a "Verifying connection" screen or game

  2. Open a popup to the target page with the ID of a sensitive button in the hash fragment

  3. The user, still holding space, will now focus the sensitive button on the target page and quickly make the space press hit the button. The main page can then close the popup again to prevent the victim from noticing

Note: The attack described above does not work on Firefox, only on Chromium-based browsers. Firefox does not see onkeydown as an interaction worthy of opening a popup, disallowing holding space from calling window.open().

Keyboard Popunder

The idea in Holding Space works, but requires the target page to load in full view of the victim, making them more likely to release space and fail the exploit. Instead, if we were able to load the target page in the background and then re-focus it when it is completely loaded, there should be no time for the victim to notice. This is an attack I described in depth in the following blog post:

While so-called "popunders" should no longer be possible in modern browsers, we can emulate them while the user is typing. While holding space spacebar, the onkeydown event is actually sent repeatedly similar to holding down any letter will write that letter repeatedly after a small second. This gives us effectively infinite user interactions and "user activation", so we can call the window.open() function without having to worry about the popup blocker.

After this routine, we can redirect the popup in the background to the target page with the sensitive button and an id= attribute. After it loads, and while the user is still holding space, we quickly focus the popup so that the user instantly presses the button without it having to load/wait.

Some example proof of concepts for different cases have been shared here, which you should be able to slightly alter for your target:

The browser's built-in popup blocker prevents our window from spawning. Any window.open() calls require or just an interaction. The documentation explains what events trigger it and what APIs are affected by this protection. In short, we need the user to click somewhere, and inside the event handler for that click, open our popup:

The popup will itself have a reference back to the main page as well. The variable holds a reference to the page that opened this window, so in our case the main page. The target page can also use this variable to detect when it is being displayed in a popup and act accordingly. In rare scenarios, you might want to prevent this detection and altered behavior. Luckily, it is very easy to revoke access to the opener variable, simply using the noopener window feature (ironically, introduced to enhance security):

With a window reference, a same-origin popup has and methods to move the window around, as well as and methods to change its bounds. These can be called at any time to change what area a window covers, no matter if it's focused:

Another surprisingly useful feature of windows is the target (2nd) argument. This sets the property for the window. One interesting behavior that this causes is that if there already exists a window with the same name, it is re-used instead of creating a new one. As normal with a popup, focus will be given to the new popup, but with this trick the new popup may be an existing window with the same name.

By opening a popup onkeydown with a page under our control (eg. an inline ) that itself also has an onkeydown sending the focus back to the main page, it only shows up for a split second before being hidden again. This is effectively a popunder!

🌐
"Transient activation"
window.opener
.moveTo()
.moveBy()
.resizeTo()
resizeBy()
window.name
Blob
window.open(url, target, windowFeatures)
Blog: Cross Window Forgery: A Web Attack VectorBlog
Article explaining an example attack of pressing a button with the spacebar
Logo
Pressing Buttons with Popups (on Twitch, LinkedIn and more) | Jorian Woltjerjorianwoltjer.com
Explaining practical attacks by holding space with popups
Logo
GitHub - JorianWoltjer/popup-research: Proof of Concepts for "Pressing Buttons with Popups (on Twitch, LinkedIn and more)"GitHub
Experiments and proof of concepts for real-world targets
Logo
Popup blocker preventing window from spawning