An rating:: easy, challenge_category:: web challenge. This is a fun little challenge introducing the concept of Server-Side Request Forgery.
Initial Observations
Technology:
- Flask Web Framework
Misc
- Flag gets set as an environment variable in the Dockerfile.
- Secret Key created at runtime in
config.py - Two Blueprints (Flask Concept for modular applications):
/← public/debug
- Two routes:
/← public/environment← only accessible if theutil.is_from_localhostcheck is passed
- The public route accepts an
urlas a request arg. It fetchesf'http://reddit.com/{url}'and returns, content, status code and headers. - The
/environmentroute returns all env vars, including the flag.
First Hypothesis
We need to somehow trick the proxy route to query the environment route, since it can only be accessed from localhost
Dive In
Lets continue inspecting the util.py functions, since the key to the challenge seems to be here.
The is_from_localhost function
The function checks if the remote_addr of the Flask Request Object is equal to 127.0.0.1. Without knowing how exactly Flask populates that field, I assume that it can’t be easily tricked. Nevertheless I had to try:
curl http://<target_ip>/environment -H "X-Forwarded-For: 127.0.0.1"But as expected it returned a 403: Not Allowed.
The proxy_req function
This function gets the http method, a byte representation of the request body (request.get_data()) and a selection of the headers (['x-csrf-token', 'cookie', 'referer']) from the request context, and the formatted reddit URL, does a request using python requests, then checks the requested URL as well as the response.url with the is_safe_url function, and then returns response and headers.
Mental Notes
Seeing that the is_safe_url check is made after the request is performed made me suspicious. Furthermore I might have to research what the
response.urlis and in which cases it differs from the requested URL.
The is_safe_url function
Checks if any of [‘localhost’, ‘127.’, ‘192.168.’, ‘10.’, ‘172.’] is in url . What jumps out to me is that 0.0.0.0 is missing in this list.
Attack Path
From looking at the source code, we know that we can get the flag, by
- making a request to
/debug/environment- from localhost
- by controlling the
urlpart inf'http://reddit.com/{url}'- bypassing the
is_safe_urlmethod by addressing localhost via0.0.0.0
I knew that this type of attack is called Server-Side Request Forgery or SSRF. Since this is my first SSRF, I had a quick chat about it and ChatGPT remembered me of the option to use an @ in URLs! This might be it!
Exploit
With all this information at hand, the exploitation is rather simple. This is our crafted URL:
curl http://<target_ip>/?url=%400.0.0.0%3A1337%2Fdebug%2FenvironmentEt voilà: There we have the flag (along with all other environment variables 🤯)