Follow The Rabbit - FCSC2023 Writeup
In this article, we will solve one of the hardest web challenges of the FCSC2023. Follow the Rabbit is a nginx server with a custom configuration.
Source code: SHA256(follow-the-rabbit-public.tar.gz) = 6d5af5b83e3c9d3d5bb556965440df80507406239e68ef94c03ba1482d99f411.
TL;DR
Abusing normalized URI and location to bypass regex and reach a specific location to obtain the flag.
Overview
The challenge is about bypassing an Nginx configuration to obtain the flag. The Nginx docker is defined in the following manner:
|
|
It should be noted that the current version of Nginx is 1.24.0
, and not 1.23.3
. Therefore, I attempted to find any security fixes that might exist between these versions, but nothing interesting comes up. You can view all the nginx versions on hg.nginx.org.
The Nginx configuration inside nginx.conf
contains two server
blocks but only the the port 80
is accessible (mapped on 8000
).
|
|
To summarize:
- Exposed server -
0.0.0.0:80
- Regex
~* ^(.*)$
=>"I'm late! I'm late! For a very important date!"
- Match
/
=>"Oh dear, oh dear! I shall be too late!"
- Match
/deeper
=>proxy_pass http://@deeper$uri$is_args$args;
- Regex
- Internal server -
@deeper
- Match
/deeper
=>"No time to say hello, goodbye! I'm late! I'm late! I'm late!"
- Match
/deepest
=>"$flag"
- Match
This challenge involves two steps. The first one is to bypass the regex on the exposed server to reach the /deeper
location. Consequently, our HTTP request will be fowarded to the internal server. The second steps involves directing the same HTTP request towards the /deepest
location of the internal server.
location ~* ^(.*)$
On the nginx documentation about the location directive, we can see that the matching is done after URL decoding. So, if we insert a new line (%0A
) in the path of our request, we can bypass the regex.
|
|
As you can see above, we reach the location /
! Now let’s try to reach the /deeper
location.
proxy_pass
|
|
How it works ? The $uri variable is URL decode before sending the request to the internal server.
$uri: current URI in request, normalized (decoding the text encoded in the “%XX” form).
To do some debuging, we can edit the nginx configuration to send the proxy pass to a netcat server to see the HTTP request in plaintext.
|
|
We successfully forged a valid HTTP request by adding a fake header.
normalized URI
So, our last goal is to reach the /deepest
location. To do that we need to use double URL encoding to match the /deeper
location of the first server and the location /deepest
of the second server.
- Server #1 location
/deeper
:/deeper/%252E%252E%252Fdeepest
($uri normalized) - Server #2 location
/deeper
:/deeper/%2E%2E%2Fdeepest
->/deeper/../deepest
->/deepest
(location normalized)
$uri
andlocation
arenormalized
: The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.
|
|
We can now obtain the flag !!!
|
|