FCSC2023 - Follow the Rabbit
This FCSC web challenge was really fun to solve. You had to bypass some nginx directives to reach a specific endpoint.
Description
While Alice was taking care of her garden, she stumbled upon a panicked white rabbit. In a hurry, the rabbit asked her to follow him. Without hesitation, Alice decided to pursue him into his mysterious burrow.
URL : https://follow-the-rabbit.france-cybersecurity-challenge.fr
SHA256(follow-the-rabbit-public.tar.gz
) = 6d5af5b83e3c9d3d5bb556965440df80507406239e68ef94c03ba1482d99f411
.
Resolution
For this challenge, we are provided with the following nginx configuration:
|
|
We can already tell that to reach the flag, we need to access the /deepest
endpoint, which is behind a reverse proxy.
It is impossible (for now) to get to this endpoint as a non-localhost source because it is only defined in the server block listening on port 8082, which is bound to localhost. In the server block listening on port 80, there is a location block for /deeper
that proxies requests to the deeper server running on localhost:8082
. However, the /deepest
endpoint is not reachable since requests must start with /deeper
to be accessed through this proxy.
Regex bypass
To start, we notice that trying to access the /deeper
page doesn’t work because the regular expression ^(.*)$
used in the first directive seems to catch all requests.
|
|
The documentation nginx confirms that the ~*
prefix indicates the use of a regex. Referring to regex101, we see that the regex matches everything except \n
.
We adjust our request accordingly and successfully bypass the first filter.
|
|
Proxy pass $URI
Now that we’ve reached the /
directive, we’ll try to access /deeper
. We know that our request must start with /deeper
to enter the directive, but we still need a \n
to bypass the initial regex. We observe in the docker logs that there are two requests, with one sent from localhost. This indicates that we are indeed passing through the /deeper
directive, but the request must be malformed since we don’t receive a response.
|
|
We change the listening port from 8082 to 8083, then rebuild the Docker container with docker-compose down && docker-compose build && docker-compose up
, connect to it with a shell docker exec -it public-follow-the-rabbit-1 sh
and run netcat in listening mode.
|
|
We see that the \n
creates a new request, so all we need is a valid HTTP request as shown below.
Don’t forget (like I did) the two line returns to mark the end of the request.
|
|
We get a valid request with the payload /deeper%20HTTP/1.0%0AHost:%20localhost%0A%0AGET%20/
, which allows us to move on to the final step.
The end of the payload
GET%20/
is not necessary as we will not receive the response from the second request.
|
|
Double encoding
There is an explanation that I deliberately left out in the previous section. Why did the line return create two requests instead of just one, as in the first step ?
This is due to the fact that the request is normalized, as stated in the documentation. Nginx interprets %XX
hexadecimal values.
We also learn that it decodes .
, ..
and /
.
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 will use double URL encoding to get a payload as /deeper/../deepest
, which will only be interpreted as /deepest
once it gets through the proxy. For example, when nginx decodes the URL, it first decodes %25
as a percent sign (%
), and then decodes the remaining 2f
as a forward slash (/
).
The final request will be https://follow-the-rabbit.france-cybersecurity-challenge.fr/deeper%252F%252E%252E%252Fdeepest%20HTTP/1.0%0AHost:%20localhost%0A%0AGET%20/
Flag
FCSC{429706b083581875b3af87c239f3d42a44d39e63991c4a2a3f63cde5d86b1b23}