XSS Iframe Traps
Longer Running XSS Payloads
The idea for this attack came from discussions with my clever colleague @n00py who has used similar techniques to fake login pages on pages with XSS. This technique allows him to capture credentials from users.
For demonstration purposes throughout the blog (until the end), we’ll keep the iframe as a smaller overlay of the actual page instead of displaying it fullscreen. We’ll also set the background color of the XSS landing page to pink, in order to make it easier to discern the real page with XSS from the iframe where the user is trapped.
Once we close the alert box, the trap code will start running. An iframe will be created with a different page in the application as the starting URL. I chose the homepage for example, but the appropriate page may depend on your pretext in a social engineering campaign.
In order to fake the URL address bar, our iframe trap has registered a number of events in the iframe page itself. When something in that iframe changes, the user navigates, scrolls, etc, an updateUrl() function is called. This function retrieves the URL path of the iframe, then sets it into the actual address bar using the window.history.replaceState() function call. This method is intended to allow programmers to manage the browser history but certainly has some fun side effects for attackers.
Without this code to modify the address bar, the URL will show the landing page where our XSS is running,
http://192.168.78.157/index.php/bwg_gallery/dry-run/, which could draw suspicion as the user navigates the site and the URL never changes. With our updateUrl() function, the address bar will reflect the address the user would expect, hopefully encouraging them to stay in our iframe trap.
As an example, once the user clicks the Site Admin link, the iframe will load that page, and our updateUrl() function will get called, properly faking the address bar once again.
The application and the browser will appear as though all is normal, and our XSS payload continues to run. Neat. Another fun aspect of this attack is that by using the replaceState() function to spoof the URL address, we’re also managing the history list. This means that user’s back/forward buttons will work as expected with their iframe responding, leading to the page they expect, and the URL address bar getting properly spoofed again properly.
There are, of course, a number of ways the user can inadvertently escape our iframe trap, and we lose execution of our XSS payload. They could close the browser tab, for example. Not much we can do there.
They could also manually type a URL in the address bar and hit enter. This will lead the browser to load the page where they typed, and they’ve escaped our trap. Even if they simply highlight the URL bar and hit enter to reload the page, the browser will load the spoofed URL in the address bar, and we’ll lose our trap. Similarly, with a reload request by button, context menu, or keyboard shortcut, the result will be escape rom the iframe trap.
We can combat this by partially addressing the reload issue. First, we can disable the right-click context menu, making it a bit harder to find that reload control. We need to do this on each new page loaded in our iframe, so this functionality is added into the updateUrl() function, so it’s run regularly.
A more likely reload event is going to come from a user utilizing the reload button in the browser bar near the back/forward buttons. We can’t stop this, but we can prevent it from breaking our iframe trap. This will make our trap less stealthy however.
First, we register event handlers for the mouse leaving or entering the iframe.
Because the user pressing the reload button will load the page displayed in the URL address bar, what we’ll do when the user’s mouse leaves the iframe is save the URL that’s currently showing in the address bar and replace it with the actual XSS landing page. Hopefully (fingers crossed), the user doesn’t notice the change. The ‘fake’ URL is saved to session storage in the user’s browser.
Now, if the user clicks the refresh button, our trap is escaped. However, the page with the XSS is reloaded, bringing up a fresh iframe trap.
The startup code for our iframe trap checks the session storage of the browser to see if there is a saved page. If there is, it sets the iframe location to that URL, making it appear to the user that they simply refreshed the page in the iframe. Everything appears to be working as expected.
That is an easy way of trying to keep your XSS payload running longer. The proof-of-concept is written fairly generically, but you might need to tweak it a bit for your client’s applications. Always test to see if things are working as you would expect. The proof-of-concept also lacks any malicious code. It simply demonstrates the trap technique.
Further interesting XSS persistence/traps can be found in the BeEF browser exploitation framework. If you have any issues or ideas on how this can be improved, my DMs are always open @hoodoer.
- https://gist.github.com/hoodoer/6b005b501dc0ff90f8b00b90611bc2bb (XSS trap sourcecode)