Frameset XSS – Not my tag, not my problem

I recently came across a challenging frameset xss, and I wanted to sure how I was able to exploit it.


First, imagine the following application.

root@kali:~/frameset# cat frameset.php

<title>Vulnerable Frameset Example</title>

<frameset cols=21%,* framespacing=1>
<frame name=left src="left.html" frameborder=1 framespacing=0>
<frame name=right src="<?php echo $_GET['right'] ?>" frameborder=0 framespacing=0>

Can't see me

root@kali:~/frameset#  cat left.html
I'm the left frame!
root@kali:~/frameset#  cat right.html
And I'm the right frame?

If we visit the page, we get the following view.

Frameset XSS - Vulnerable

To start, this is a simple, but to the point application. We can verify that PHP is using the GET parameter by inputting an invalid one.

Frameset XSS - Not Found

As the application is writing directly into a tag, we could just escape the current tag, and placing a script tag.

To verify that we can write arbitrary HTML, we can send a test tag.

Frameset XSS - Arbitrary tags

Unfortunately, if we look into the source of the page and Firefox's debugger, we see the first sign of trouble.

Frameset XSS - HTML Errors

For those of you not aware, the frameset tag is a wonderful tag, and one of the few in HTML that actually cares what is around it. For starters, if you have a frameset tag, your HTML document cannot have a body. Additionally, the only tags allowed inside of a frameset are "frame" and "noframes", that is it.

To verify that our first idea wouldn't work, we can try exiting the frame tag and creating a script tag. As you can see, the debugger gives us an error and we do not receive an alert.

Frameset XSS - Script fail

Option 1

Option one to solve this problem, is to use our XSS polyglot. If we use this as a new frame src, then the page will load the HTML into the frame, and our script will fire off.

To verify this, I finished the current frame tag, and created a new one. As you can see, the frameset XSS fired off!

Frameset XSS - Polyglot

The source of the page verified that there we no longer any errors, and that we had a new frame with the source of our XSS domain.

Frameset XSS - Polyglot source

Unfortunately, this attack option does come with a downside.

If we add the following code to our application, it will also have a "secret" cookie value.

$cookie_name = "secret";
$cookie_value = "thisISmy$3cr3t";
setcookie($cookie_name, $cookie_value, time() + (86400 * 30), "/"); // 86400 = 1 day

We can verify that the application sets the cookie by running alert(document.cookie) in the browser console.

Frameset XSS - Cookie

Unfortunately, if we attempt to get access to the cookie from our XSS payload on, we are unable to get to it. The reason for this is that we are violating the Same-origin policy. As we are loading our payload externally into a frame, it is coming from directly instead of the local application. In this case, we would not be able to steal a user's cookies or session.

Frameset XSS - Cookie fail

Option 2

Unfortunately, since option one doesn't bypass the SOP, we'll have to move on to option two.

As we can create new frames, we can use a JavaScript URI, and code our payload inline.

For example:

right.html" frameborder=1 framespacing=0><frame src="javascript:alert(1)

After we load the page, we get our alert to fire off.

Frameset XSS - JavaScript URI

As this is local JavaScript, we no longer have to worry about violating the SOP, and could use this to grab cookies.

Option 3

If we can't, or don't, want to use a JavaScript URI, then there is still another option for us!

Since we are able to write arbitrary frame tags, we can use a data URI and write arbitrary JavaScript as our frame src.

For example, we can use the following payload.

right.html" frameborder=1 framespacing=0><frame src="data:text/html,<script type='text/javascript'>with(parent) {alert('xss');}</script>

Once we load the page, we can verify that our alert fired off!

Frameset XSS - Data URI

Additionally, inside the source of the page, we can see our newly created frame.

Frameset XSS - Data source

The main benefit of this payload over option one, is that the local application is loading the script, thereby bypassing the SOP.

Next, we can use the following payload.

right=right.html" frameborder=1 framespacing=0><frame src="data:text/html,<script type='text/javascript'>with(parent) {alert(document.cookie);}</script>

When we view the source of the page, we can see that our payload and HTML is still intact.

Frameset XSS - Data cookie

Finally, when we load the page, our frameset XSS fires off and we can steal the user's cookie!

Frameset XSS - Cookie grabbed

doyler on Githubdoyler on Twitter
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.

When he's not figuring out what cert to get next or side project to work on, he enjoys playing video games, traveling, and watching sports.

As an Amazon Associate I earn from qualifying purchases.

Common passed on this blog, I made it to a jam.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

This site uses Akismet to reduce spam. Learn how your comment data is processed.