Gatekeeping
Bypassing Nginx directive through manipulating Gunicorn WSGI variables
Description
My previous flag file got encrypted by some dumb ransomware. They didn't even tell me how to pay them, so I'm totally out of luck. All I have is the site that is supposed to decrypt my files (but obviously that doesn't work either).
Author: itszn
, Ret2 Systems
http://web.chal.csaw.io:5004
Solution
When inspecting the provided Nginx configuration, I found an interesting directive:
I think "Brad" explained it quite well, but essentially, this disallows all requests with URL paths starting with /admin/
. Nginx serves as the "front-end" forwarder that passes requests to Gunicorn, which is a WSGI server. Gunicorn is the one that serves the actual Flask application.
Interesting! Looking at the server code revealed a hidden endpoint under /admin/key
.
Clearly, we had to get to the /admin/key
endpoint to get the key. But how?
There is another interesting part of the Nginx configuration. When forwarding requests to Gunicorn, the request headers are preserved.
I began wondering if HTTP headers could somehow manipulate the processing of the URL path by Gunicorn, and found this stackoverflow thread.
Apparently, when the SCRIPT_NAME
WSGI variable is set, the SCRIPT_NAME
prefix is stripped from PATH_INFO
. According to the documentation, the SCRIPT_NAME
can be set through a HTTP header.
Interesting! Consider the following request:
Nginx first receives the request. It checks against the directives specified in the configuration file, and confirms that access is not denied (/test/admin/key
does not start with /admin
). The request is now forwarded to Gunicorn.
Gunicorn sees the SCRIPT_NAME
HTTP header, and hence uses /test
as the SCRIPT_NAME
WSGI variable. Gunicorn strips SCRIPT_NAME
from the beginning of the URL path, leaving us with /admin/key
. Therefore, /admin/key
is the final endpoint that is served by the Flask application.
Great! We have access to the /admin/key
endpoint. In order to get the decryption key, we have to suppply a key_id
.
Fortunately, the logic for generating the key_id
is already implemented in the site's JavaScript. Add a line to log the key_id
to the console:
The key_id
for the flag file is 05d1dc92ce82cc09d9d7ff1ac9d5611d
.
Using this key_id
, we can find that the decryption key is b5082f02fd0b6a06203e0a9ffb8d7613dd7639a67302fc1f357990c49a6541f3
.
The only thing left to do is to decrypt the file. I modified the /decrypt
endpoint to do this.
The flag is flag{gunicorn_probably_should_not_do_that}
.
Last updated