Frameset injection no equals – An Impossible Challenge.

Written by:

I sometimes spend time reviewing the impossible labs over at Portswigger Research and usually have fun, but not much luck trying to come up with new ways to solve them. This time is no different, but I do think I can make some small contribution to this impossible task. Documenting the impossible: Unexploitable XSS labs | PortSwigger Research

As they note in the lab description:

Injection occurs inside a frameset but before the body

We received a request from twitter about this next lab. It occurs within a frameset but before a body tag with equals filtered. You would think you could inject a closing frameset followed by a script block but that would be too easy.

View lab

Here is the crux of the problem. HTML 4 supported the frameset element. It was used, instead of a body element, to allow having multiple different pages with different body elements displayed next to each other on a page, in much the same way you could with iframes. You can’t inject random HTML into a frameset or frame, it won’t render. You can’t close the frameset and add a body because body isn’t allowed with a frameset. It’s one or the other. Note in this challenge, the equal sign is not allowed.

After researching the frameset and frame a bit, I came across the <noframes> tag which functions essentially like a <noscript> tag. If frames are not allowed by the browser, it renders the noframes tag. Historically, this was always used to render a body tag if frames couldn’t be enabled. Great! If we just inject a noframes, body, and script tag, we solve this, right? For example, usage like this was not uncommon because if you can’t display frames you need a body to display anything else.

<frameset cols="50%,50%"> 

<frame src="frame1.html">

<frame src="frame2.html">

<noframes>

<body>
<p>Your browser does not support frames. Here is the <a href="frame1.html">link to the first frame</a> and here is a <a href="frame2.html">link to the second frame</a>.
</p>
</body>
</noframes>
</frameset>
<!-- Does that mean this will work? -->
<frameset>
<noframes>
<body><script>alert(1)</script></body>
</noframes>
</frameset>

Well, no. There are a couple of issues here.

1. There is no way I can find to ever get the noframes to render anything ever. If you can, I think this should be a viable solution to the impossible lab. I tried using various HTML doc types, but it never triggers because the browser supports frames just fine. There are not even settings to disable frames that I could find. Next, I reasoned that if you disallow frame-src in the CSP it would start rendering because they literally are not allowed, but no, the browser displays its own content in that case and not the noframes. Note, there may be the odd browser specific error in frameset that could trigger noframes, but I’m not spending the time looking for it.

2. And while you should be able to do something like <noframes><body><script>alert… you simple can’t anymore. Noframes in Chrome, Firefox, Opera etc all render as text these days, using the same mechanism that renders noscript as text. I didn’t check Safari. I didn’t check other browsers.

Does this leave us completely out of luck? Well, sorta. These are impossible labs after all.

Client-Side Template Injection

Not to be completely outdone, I will add this into the mix. Even though you can’t inject anything other than a noframes element that contains text, if you have AngularJS on the page, you can set that noframes element to <noframes ng-app> and then inject {{template strings}} into the noframes body and angular will still happily execute it. While AngularJS is definitely a legacy technology at this point, bugs are still found in major vendors that still use it.

server.ionatomics.org/noframes.html

Nerdy Data tells me there are 6000+ sites with frameset and AngularJS so maybe there is a chance. It’s something. I’ll take what I can get with these challenges. Other frame works might be used for a similar purpose – I didn’t dig into it but maybe Handlebars or Vue depending on specific configuration.

So, there you have it. If your only injection point is inside of a frameset, and equals is filtered, and AngularJS is available (or another client-side template framework), you can easily solve this.

Relative Path Overwrite

If the site is using a frameset, maybe it’s using an outdated Doctype. If that’s the case, it could be worth checking if something related to Relative Path Overwrite RPO (thespanner.co.uk) by https://twitter.com/garethheyes could work. Again, a long shot, but this is a hard problem.

In both cases (Client-side template injection and CSS injection into the <noframes>), because we can inject plaintext, and then convince another parser that is NOT the HTML parser to execute JavaScript code we can get JavaScript execution. Well, there are tons of other parsers (SVG, XML, Emoji, PDF, Rich Text, Markdown to name a few) and I wonder if any of them could be brought into the fold to solve similar problems.

Leave a comment