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}