Source Code Analysis
The goal is to access the GET /flag endpoint which calls an executable that prints out the flag. It’s guarded by 2 checks:
- if session[:user] == "admin"
- if req.ip == "127.0.0.1"

There’s also an interesting POST /download endpoint. It accepts any session but is also guarded by the if req.ip == "127.0.0.1" check. It serves a file based on filename from our request, which leads to LFI.

Bypassing 127.0.0.1 Restriction
The 127.0.0.1 can be trivially bypassed by adding an X-Forwarded-For: 127.0.0.1. This works on local instances but it didn’t work on the remote instance during the CTF. As it turned out, the remote instance was behind Google Load Balancer which appended additional forwarded IPs to the header. The server used the last IP in the header for comparison (check the references). Thankfully, Rack supports the Forwarded header, it takes precedence over X-Forwarded headers and is not appended to by the load balancer. In conclusion, we can use Forwarded: for=127.0.0.1 to bypass this restriction.
Leaking Session Secret and Forging Admin Session
We use the 127.0.0.1 bypass and the POST /download endpoint to reveal the contents of server.rb. This file includes a constant server secret used to forge sessions.

We HTML decode the secret.

Now, we could analyze the source code and write our own script for creating the session, but we just opt for replacing the secret in our local instance with the secret from remote.

Finally, we log in locally as admin, then copy the session, and use it on remote to get the flag.

The final flag is: CSCTF{Y0u_G0t_1t_G00d_J0b}.