Writeup - Root-Me CTF 10k - Proxifier
- Difficulty : 499 points | 10 solves
- Description : How using several URL parser on the same input could be dangerous?
- Author: Kévin_Mizu
🕵️ Recon
Like with simple_login, for this challenge we have access to the code. So, the first step is to take a look into it:
|
As we can see, the website has an endpoint /proxy
which take as an input the url
get parameter. Then, our input is sent to the getURL
function which is where all the magic occurs. This function will do the following:
🥪 Protocol Confusion
As we can see with the above diagram, if parse-url@7.0.2
return the file://
wrapper we have a chance to get an LFI. But this could be really weird as the first parser must return https
which means that 2 parser need to return a different protocol value…
At this point there is two way to find this vulnerability: trying random input into both parser and wait to have the desire output or dive into parse-url@7.0.2
source code and try to find the issue. In this writeup, we will focus on the second one. By going to npm, we could have this github repository: link.
On this, we can get the source code which is really really short. (87 lines)
|
Inside it, we could see that the most important part of the parsing is done by the function parsePath(url)
. Looking into the code, we could find that it comes from another library (parse-path) developed by the same person. Thus, let’s read his code! (source)
|
And this is where is starting to be interesting. As we can see, if our input crash during the new URL
parsing, it will return the file://
wrapper by default! Let’s try this with a simple invalid URL:
|
Output:
|
As we can see, we manage to get the file://
wrapper.
The next step is then to have https://
with url-parse
and file://
with parse-url
. By looking to some reports on url-parse
, we could learn that the library assume that it can return invalid host.
- https://huntr.dev/bounties/36ea922b-d704-499c-b1ed-2f0f5e8be7be/
- https://huntr.dev/bounties/7ae34687-d91a-4a29-a880-6a467c67dbd2/
Thus, we know that url-parse
allows invalid host and parse-url
return file://
wrapper for invalid URL. Combinating both information, we could quickly find a payload that valid all the checks:
https://:root-me.org/
PS: This works only before 7.0.2 as there is no check of parse_failed
in the main library script.
🏠 Host Confusion
Well, now that we have a bypass for the protocol
part, we need to find a way to go around the host
check. This one is done into:
|
Reading the MDN documentation about this API (source), we could learn that depending of the input, the host could change. (example)
The problem here is that there is no example with a path in the first argument which leads to this behavior… So, it is impossible? Obviously not, trying the well-known tricks //
and we get exactly what we want! 🎉
|
Output:
|
📄 LFI
Now that we have everything, let’s try to get /etc/passwd
:
Payload: https://:root-me.org//127.0.0.1/etc/passwd
|
Perfect! It works! Now, we need to get the flag and if we remember well the content of the challenge script, it contains this path:
|
So, we need to find a way to leak its name. To do so, we will use the proc
linux trick: https://:root-me.org//127.0.0.1/proc/self/cmdline
which returns the executable path of the current proccess (the challenge in the LFI context)
|
Now we can get the challenge file: https://:root-me.org//127.0.0.1/var/app/you_wont_guess_it.js
|
🎉 Flag
The last step is to get the flag: https://:root-me.org//127.0.0.1/var/app/a49b4e26e4b6b4638f225fb342a645ce/flag.txt
:D
Flag: RM{T4k3_C4R3_0f_Y0uR_URL_P4rs3R}
🎉