[TJCTF2020] – Admin Secrets

Given a website, where a person can login and register. Both are irrelevant to the challenge, so I will cut the chase. The other feature, the ‘relevant’ feature of the challenge, is a feature that allows the user to write a note and even send the note to an admin (evil laugh). It isn’t hurt to think that this might be an XSS challenge, and I did that.

To start working on the XSS, I submitted a simple fuzzing payload as a sanity check:

and the mighty alert box appeared.

Since XSS is not a dream anymore, the next step is to do the basic: steal the admin cookie. ArkAngels had already done the hard work for this by the time I started working on this challenge. Long story short, I spin up a RequestBin to capture the stolen cookie and submitted this payload:

After the payload is submitted, I reported my post to the admin. The admin visited, and here’s the result:

The cookie is nothing but a hint to check the admin console. At first, I thought I need to capture everything that is being printed into the Developer Console Window, but then I noticed that inside the page’s HTML, there is a div tag with admin_console class. So that should be the next step.

Then I tried to smuggle the content of the admin_console tag from the admin (since they said “only the admin can see it”). I modified and submitted this payload for that purpose:

But that shouldn’t be working because document.getElements* functions return a live collection (explanation here). So while the page is still loading, my payload is already being executed, hence there might be no tag with the class named admin_console yet. That’s why I need to make sure that the page is loaded before trying to access the innerHTML of the admin_console tag.

I modified the payload into something like this:

By wrapping the payload inside a window.addEventListener("load") function, the payload will only be executed after the whole page has loaded, including all dependent resources such as stylesheets and images.

And here’s the result:

The value is kinda gibberish because I used btoa() function to create a Base64-encoded ASCII string from a binary string (in case something unprintable appeared on the admin’s POV of the page). After I decoded the base64, this is the result:

I also had the chance to modify the payload to smuggle the entire page using document.documentElement property, instead of just the admin_console tag content. Here’s the modified payload:

And here’s the HTML source code of the entire page, for the sake of your clarity:

With the knowledge of the entire page, I noticed that there are some interesting things. Let me break it down for you:

  1. There are two delete buttons (or <a> tags, technically) that redirects the admin to /button endpoint when clicked, but it seems to have no function at all (they said it’s not yet implemented)

  2. There is an Access Flag button, which when clicked will run an AJAX GET request to /admin_flag endpoint. The response of the request (which is probably the flag itself) will be displayed on a tag with a class named responseAlert
  3. The /admin_flag endpoint itself is pretty tricky. When I tried to visit the endpoint directly as myself (not an admin), it says that only the admin can access them. 

By that information, I continue exploiting the XSS vulnerability to make the admin perform a request to /admin_flag and send the response to my RequestBin endpoint. That could be achieved simply by calling the f() function or emulating a button click against the Access Flag button, then take the content of the responseAlert tag where the response from /admin_flag endpoint would be placed.

Here’s the payload:

I used setInterval because I reckon there is a possibility that the AJAX request to /admin_flag
might not be finished instantly. I’m afraid there might be a slight delay until the response is placed inside the responseAlert tag to be taken (it’s an Asynchronous-JAX after all). So the payload will take whatever it is inside the responseAlert tag and perform a request to my RequestBin endpoint, carrying the (hopefully) flag. The operation will run every second until three seconds have passed (notice that I also used setTimeout and clearInterval to stop my setInterval).

And finally, here’s the result:

Even when I had the admin to take the flag and send it back to me, I still couldn’t get the flag. As you can see there, there must be some filters. I cannot use <script> tags, no quotes, and no
parentheses. At this point, I was pretty confident that the payload is working, and this is just another obstacle that I need to see through.

To be able to execute Javascript with certain tokens being banned, I thought of some ways. At first, I thought I could use JSFuck (read here) to obfuscate the payload and bypass the check, but JSFuck does use parentheses, so that’s a no. Instead, I hid the entire payload by converting it into the HTML Entities equivalent for each character. You can read more about HTML Entities here.  Also, to avoid using <script> tags to execute the payload, I used an img tag instead and put the payload inside the onload attribute.

When I was working on this challenge I build a script to convert my payload into the HTML Entities format, but you also can use a tool for that (like this). Here’s the script:

And here’s the final payload:

Submitted the payload, send it to the admin, and voila flag:

Flag is tjctf{st0p_st3aling_th3_ADm1ns_fl4gs}

Leave a Reply

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