1450 lines
36 KiB
Ruby
1450 lines
36 KiB
Ruby
#
|
|
# Web assessment for the metasploit framework
|
|
# Efrain Torres - et[ ] metasploit.com 2010
|
|
#
|
|
# $Id$
|
|
# $Revision$
|
|
#
|
|
|
|
require 'rabal/tree'
|
|
require 'rexml/document'
|
|
require 'tempfile'
|
|
require 'rubygems'
|
|
require 'active_record'
|
|
|
|
module Msf
|
|
|
|
#
|
|
# Constants
|
|
#
|
|
|
|
WMAPVersion = "0.9"
|
|
WMAPAuthor = "ET et [ ] metasploit.com"
|
|
WMAPBanner = "\n========[ WMAP v#{WMAPVersion} ]=========\n= #{WMAPAuthor} =\n=============================="
|
|
WMAP_PATH = '/'
|
|
WMAP_CHECK = true
|
|
WMAP_RUNEXPL = true
|
|
WMAP_EXITIFSESS = true
|
|
WMAP_SHOW = 2**0
|
|
WMAP_EXPL = 2**1
|
|
|
|
PROXY_CMDLINE = "../ratproxy/ratproxy"
|
|
PROXY_DEFAULTOPTS = " -a -v " + File.join( ENV.fetch('HOME'), '.msf3') + " -b sqlite3.db"
|
|
|
|
CRAWLER_CMDLINE = "ruby " + File.join(Msf::Config.install_root,"tools", "msfcrawler.rb")
|
|
CRAWLER_DEFAULTOPTS = ""
|
|
|
|
# Command line does not allow multiline strings so we pass headers header:valueSEPARATOR
|
|
STR_HEADER_SEPARATOR = '&'
|
|
|
|
# Exclude files can be modified by setting datastore['WMAP_EXCLUDE_FILE']
|
|
WMAP_EXCLUDE_FILE = '.*\.(gif|jpg|png*)$'
|
|
|
|
RUN_WMAP_SERVER = true
|
|
RUN_WMAP_DIR_FILE = true
|
|
RUN_WMAP_QUERY = true
|
|
RUN_WMAP_BODY = true
|
|
RUN_WMAP_HEADERS = true
|
|
RUN_WMAP_UNIQUE_QUERY = true
|
|
RUN_WMAP_GENERIC = true
|
|
|
|
|
|
class Plugin::Wmap < Msf::Plugin
|
|
class WmapCommandDispatcher
|
|
include Msf::Ui::Console::CommandDispatcher
|
|
|
|
def name
|
|
"Wmap"
|
|
end
|
|
|
|
#
|
|
# The initial command set
|
|
#
|
|
def commands
|
|
{
|
|
"wmap_website" => "List website structure",
|
|
"wmap_targets" => "Targets in the database",
|
|
"wmap_sql" => "Query the database",
|
|
"wmap_run" => "Automatically test/exploit everything",
|
|
"wmap_proxy" => "Run mitm proxy",
|
|
"wmap_crawl" => "Crawl website",
|
|
"wmap_attack" => "Crawl and Test",
|
|
}
|
|
end
|
|
|
|
def cmd_wmap_attack(*args)
|
|
aurl = args.shift
|
|
|
|
puri = URI.parse(aurl)
|
|
tssl = (puri.scheme == "https") ? true : false
|
|
|
|
if (puri.host.nil? or puri.host.empty?)
|
|
print_error( "Error: target http(s)://target/path")
|
|
else
|
|
|
|
crawldefaultopts = ""
|
|
rundefaultopts = ""
|
|
|
|
crawlopts = crawldefaultopts + " -t " + aurl + " " + args.join(" ")
|
|
runopts = rundefaultopts + " -t " + aurl + " " + args.join(" ")
|
|
|
|
#print_status("Crawling")
|
|
#cmd_wmap_crawl(crawlopts)
|
|
|
|
print_status("Reloading targets")
|
|
cmd_wmap_targets("-r")
|
|
|
|
print_status("Selecting target")
|
|
|
|
tid = -1
|
|
framework.db.each_target do |tgt|
|
|
if tgt.host == puri.host and tgt.port.to_i == puri.port.to_i
|
|
tid = tgt.id
|
|
print_status("Target ID: #{tid}")
|
|
end
|
|
end
|
|
|
|
seltgt = framework.db.get_target(tid)
|
|
if seltgt == nil
|
|
print_error("Target id not found.")
|
|
else
|
|
seltgt.selected = 1
|
|
seltgt.save
|
|
print_status("Testing")
|
|
cmd_wmap_run("")
|
|
end
|
|
end
|
|
end
|
|
|
|
def cmd_wmap_website(*args)
|
|
print_status("Website structure")
|
|
if selected_host == nil
|
|
print_error("Target not selected.")
|
|
else
|
|
print_status("#{selected_host}:#{selected_port} SSL:#{selected_ssl}")
|
|
print_tree(load_tree)
|
|
end
|
|
print_status("Done.")
|
|
end
|
|
|
|
def cmd_wmap_targets(*args)
|
|
# Default behavior to handle hosts names in the db as RHOSTS only
|
|
# accepts IP addresses
|
|
accept_hostnames = true
|
|
resolv_hosts = false
|
|
|
|
|
|
args.push("-h") if args.length == 0
|
|
|
|
while (arg = args.shift)
|
|
case arg
|
|
when '-a'
|
|
target_url = args.shift
|
|
|
|
if target_url == nil
|
|
print_error("URI required (http://<user:pass>@host</uri>)")
|
|
return
|
|
else
|
|
puri = uri_parse(target_url)
|
|
|
|
scheme, authority, path, query = puri[2], puri[4], puri[5], puri[7]
|
|
|
|
if(not authority)
|
|
print_error("URI required (http://<user:pass>@host</uri>)")
|
|
return
|
|
end
|
|
|
|
uri_ssl= 0
|
|
if scheme == 'https'
|
|
uri_ssl = 1
|
|
end
|
|
|
|
uri_auth = authority.split(':')
|
|
|
|
uri_host = uri_auth[0]
|
|
|
|
uri_port = 80
|
|
if uri_auth[1]
|
|
uri_port = uri_auth[1]
|
|
end
|
|
|
|
uri_path = path
|
|
if path == nil or path == ''
|
|
uri_path = '/'
|
|
end
|
|
|
|
if Rex::Socket.dotted_ip?(uri_host)
|
|
hip = uri_host
|
|
framework.db.create_target(hip, uri_port, uri_ssl, 0)
|
|
print_status("Added target #{hip} #{uri_port} #{uri_ssl}")
|
|
|
|
framework.db.create_request(hip,uri_port,uri_ssl,'GET',uri_path,'',query,'','','','')
|
|
print_status("Added request #{uri_path} #{query}")
|
|
else
|
|
if accept_hostnames
|
|
framework.db.create_target(uri_host, uri_port, uri_ssl, 0)
|
|
print_status("Added target #{uri_host} #{uri_port} #{uri_ssl}")
|
|
|
|
framework.db.create_request(uri_host,uri_port,uri_ssl,'GET',uri_path,'',query,'','','','')
|
|
print_status("Added request #{uri_host} #{query}")
|
|
else
|
|
print_error("RHOSTS only accepts IP addresses: #{req.host}")
|
|
|
|
if resolv_hosts
|
|
hip = Rex::Socket.resolv_to_dotted(req.host)
|
|
|
|
framework.db.create_target(hip, uri_port, uri_ssl, 0)
|
|
print_status("Added target #{hip} #{uri_port} #{uri_ssl}")
|
|
|
|
framework.db.create_request(hip,uri_port,uri_ssl,'GET',uri_path, '',query,'','','','')
|
|
print_status("Added request #{uri_path} #{query}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
when '-p'
|
|
print_status(" Id. Host\t\t\t\t\tPort\tSSL")
|
|
|
|
framework.db.each_target do |tgt|
|
|
if tgt.ssl == 1
|
|
usessl = "[*]"
|
|
else
|
|
usessl = ""
|
|
end
|
|
|
|
maxcols = 35
|
|
cols = maxcols - tgt.host.length
|
|
|
|
thost = "#{tgt.host.to_s[0..maxcols]}"+(" "*cols)
|
|
|
|
if tgt.selected == 1
|
|
print_status("=> #{tgt.id}. #{thost}\t#{tgt.port}\t#{usessl}")
|
|
else
|
|
print_status(" #{tgt.id}. #{thost}\t#{tgt.port}\t#{usessl}")
|
|
end
|
|
end
|
|
print_status("Done.")
|
|
when '-r'
|
|
framework.db.delete_all_targets
|
|
framework.db.each_distinct_target do |req|
|
|
if Rex::Socket.dotted_ip?(req.host)
|
|
framework.db.create_target(req.host, req.port, req.ssl, 0)
|
|
print_status("Added. #{req.host} #{req.port} #{req.ssl}")
|
|
else
|
|
if accept_hostnames
|
|
framework.db.create_target(req.host, req.port, req.ssl, 0)
|
|
print_status("Added host #{req.host}")
|
|
else
|
|
print_error("RHOSTS only accepts IP addresses: #{req.host}")
|
|
|
|
if resolv_hosts
|
|
hip = Rex::Socket.resolv_to_dotted(req.host)
|
|
framework.db.create_target(hip, req.port, req.ssl, 0)
|
|
print_status("Added host #{req.host} resolved as #{hip}.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
when '-s'
|
|
framework.db.each_target do |tgt|
|
|
tgt.selected = 0
|
|
tgt.save
|
|
end
|
|
seltgt = framework.db.get_target(args.shift)
|
|
if seltgt == nil
|
|
print_error("Target id not found.")
|
|
else
|
|
seltgt.selected = 1
|
|
seltgt.save
|
|
end
|
|
when '-h'
|
|
print_status("Usage: wmap_targets [options]")
|
|
print_line("\t-h Display this help text")
|
|
print_line("\t-c [url] Crawl website (msfcrawler)")
|
|
print_line("\t-p Print all available targets")
|
|
print_line("\t-r Reload targets table")
|
|
print_line("\t-s [id] Select target for testing")
|
|
print_line("\t-a [url] Add new target")
|
|
|
|
print_line("")
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
def cmd_wmap_sql(*args)
|
|
qsql = args.join(" ")
|
|
|
|
args.push("-h") if args.length == 0
|
|
|
|
while (arg = args.shift)
|
|
case arg
|
|
when '-h'
|
|
print_status("Usage: wmap_sql [sql query]")
|
|
print_line("\t-h Display this help text")
|
|
|
|
print_line("")
|
|
return
|
|
end
|
|
end
|
|
|
|
print_line("SQL: #{qsql}")
|
|
|
|
begin
|
|
res =framework.db.sql_query(qsql)
|
|
res.each do |o|
|
|
line = ''
|
|
o.each do |k, v|
|
|
if v
|
|
line << v
|
|
end
|
|
line << '|'
|
|
end
|
|
print_line(line)
|
|
end
|
|
rescue ::Exception
|
|
print_error("SQL Error #{$!}")
|
|
return
|
|
end
|
|
end
|
|
|
|
#
|
|
# A copy of the shotgun approach to website exploitation
|
|
#
|
|
def cmd_wmap_run(*args)
|
|
|
|
stamp = Time.now.to_f
|
|
mode = 0
|
|
|
|
eprofile = []
|
|
using_p = false
|
|
|
|
args.push("-h") if args.length == 0
|
|
|
|
while (arg = args.shift)
|
|
case arg
|
|
when '-t'
|
|
mode |= WMAP_SHOW
|
|
when '-e'
|
|
mode |= WMAP_EXPL
|
|
|
|
profile = args.shift
|
|
|
|
if profile
|
|
print_status("Using profile #{profile}.")
|
|
|
|
begin
|
|
File.open(profile).each do |str|
|
|
if not str.include? '#'
|
|
# Not a comment
|
|
modname = str.strip
|
|
if not modname.empty?
|
|
eprofile << modname
|
|
end
|
|
end
|
|
using_p = true
|
|
end
|
|
rescue
|
|
print_error("Profile not found or invalid.")
|
|
return
|
|
end
|
|
else
|
|
print_status("Using ALL wmap enabled modules.")
|
|
end
|
|
|
|
|
|
when '-h'
|
|
print_status("Usage: wmap_run [options]")
|
|
print_line("\t-h Display this help text")
|
|
print_line("\t-t Show all matching exploit modules")
|
|
print_line("\t-e [profile] Launch profile test modules against all matched targets.")
|
|
print_line("\t No profile runs all enabled modules.")
|
|
|
|
print_line("")
|
|
return
|
|
end
|
|
end
|
|
|
|
if selected_host == nil
|
|
print_error("Target not selected.")
|
|
return
|
|
end
|
|
|
|
# WMAP_DIR, WMAP_FILE
|
|
matches = {}
|
|
|
|
# WMAP_SERVER
|
|
matches1 = {}
|
|
|
|
# WMAP_QUERY
|
|
matches2 = {}
|
|
|
|
# WMAP_BODY
|
|
matches3 = {}
|
|
|
|
# WMAP_HEADERS
|
|
matches4 = {}
|
|
|
|
# WMAP_UNIQUE_QUERY
|
|
matches5 = {}
|
|
|
|
# WMAP_GENERIC
|
|
matches10 = {}
|
|
|
|
# EXPLOIT OPTIONS
|
|
opt_str = nil
|
|
bg = false
|
|
jobify = false
|
|
|
|
[ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|
|
|
|
|
# Scan all exploit modules for matching references
|
|
mtype[0].each_module do |n,m|
|
|
e = m.new
|
|
|
|
# Only include wmap_enabled plugins
|
|
if e.respond_to?("wmap_enabled")
|
|
|
|
penabled = e.wmap_enabled
|
|
|
|
if penabled
|
|
if not using_p or eprofile.include? n.split('/').last
|
|
#
|
|
# First run the WMAP_SERVER plugins
|
|
#
|
|
case e.wmap_type
|
|
when :WMAP_SERVER
|
|
if RUN_WMAP_SERVER
|
|
matches1[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_QUERY
|
|
if RUN_WMAP_QUERY
|
|
matches2[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_BODY
|
|
if RUN_WMAP_BODY
|
|
matches3[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_HEADERS
|
|
if RUN_WMAP_HEADERS
|
|
matches4[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_UNIQUE_QUERY
|
|
if RUN_WMAP_UNIQUE_QUERY
|
|
matches5[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_GENERIC
|
|
if RUN_WMAP_GENERIC
|
|
matches10[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
when :WMAP_DIR, :WMAP_FILE
|
|
if RUN_WMAP_DIR_FILE
|
|
matches[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true
|
|
end
|
|
else
|
|
# Black Hole
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Handle modules that need to be run before all tests, once usually again the web server.
|
|
# :WMAP_SERVER
|
|
#
|
|
idx = 0
|
|
matches1.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# For exploits
|
|
#
|
|
payload = mod.datastore['PAYLOAD']
|
|
encoder = mod.datastore['ENCODER']
|
|
target = mod.datastore['TARGET']
|
|
nop = mod.datastore['NOP']
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins that only need to be
|
|
# launched once.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
if wtype == :WMAP_SERVER
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
|
|
if session
|
|
stat = '[*]'
|
|
|
|
if (session == Msf::Exploit::CheckCode::Vulnerable)
|
|
stat = '[+]'
|
|
end
|
|
|
|
print_line(stat + ' ' + session[1])
|
|
|
|
#
|
|
# Exploit if WMAP_RUNEXPL
|
|
#
|
|
|
|
if (session == Msf::Exploit::CheckCode::Vulnerable) and WMAP_RUNEXPL
|
|
print_status("Exploiting...")
|
|
|
|
begin
|
|
session = mod.exploit_simple(
|
|
'Encoder' => encoder,
|
|
'Payload' => payload,
|
|
'Target' => target,
|
|
'Nop' => nop,
|
|
'OptionStr' => opt_str,
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => jobify)
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Exception => e
|
|
print_error("Exploit failed: #{e.class} #{e}")
|
|
if(e.class.to_s != 'Msf::OptionValidateError')
|
|
print_error("Call stack:")
|
|
e.backtrace.each do |line|
|
|
break if line =~ /lib.msf.base.simple/
|
|
print_error(" #{line}")
|
|
end
|
|
end
|
|
end
|
|
|
|
# If we were given a session, let's see what we can do with it
|
|
if (session)
|
|
|
|
# If we aren't told to run in the background and the session can be
|
|
# interacted with, start interacting with it by issuing the session
|
|
# interaction command.
|
|
if (bg == false and session.interactive?)
|
|
print_line
|
|
|
|
driver.run_single("sessions -q -i #{session.sid}")
|
|
# Otherwise, log that we created a session
|
|
else
|
|
print_status("Session #{session.sid} created in the background.")
|
|
end
|
|
# If we ran the exploit as a job, indicate such so the user doesn't
|
|
# wonder what's up.
|
|
|
|
if WMAP_EXITIFSESS
|
|
return
|
|
end
|
|
elsif (jobify)
|
|
print_status("Exploit running as background job.")
|
|
# Worst case, the exploit ran but we got no session, bummer.
|
|
else
|
|
print_status("Exploit completed, but no session was created.")
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
print_error("Check failed: The state could not be determined.")
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
|
|
else
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Handle modules to be run at every path/file
|
|
# WMAP_DIR, WMAP_FILE
|
|
#
|
|
idx = 0
|
|
matches.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins that only need to be
|
|
# launched once.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
#
|
|
#Here is where the fun begins
|
|
#
|
|
test_tree = load_tree()
|
|
test_tree.each do |node|
|
|
|
|
testpath = Pathname.new(node.current_path)
|
|
strpath = testpath.cleanpath(false).to_s
|
|
|
|
#
|
|
# Fixing paths
|
|
#
|
|
|
|
if node.is_leaf? and not node.is_root?
|
|
#
|
|
# Later we can add here more checks to see if its a file
|
|
#
|
|
else
|
|
if node.is_root?
|
|
strpath = "/"
|
|
else
|
|
strpath = strpath.chomp + "/"
|
|
end
|
|
end
|
|
|
|
strpath = strpath.gsub("//", "/")
|
|
#print_status("Testing path: #{strpath}")
|
|
|
|
#
|
|
# Launch plugin depending module type.
|
|
# Module type depends on main input type.
|
|
# Code may be the same but it depend on final
|
|
# versions of plugins
|
|
#
|
|
|
|
case wtype
|
|
when :WMAP_FILE
|
|
if node.is_leaf? and not node.is_root?
|
|
#
|
|
# Check if an exclusion regex has been defined
|
|
#
|
|
if self.framework.datastore['WMAP_EXCLUDE_FILE']
|
|
excludefilestr = self.framework.datastore['WMAP_EXCLUDE_FILE']
|
|
else
|
|
excludefilestr = WMAP_EXCLUDE_FILE
|
|
end
|
|
|
|
if not strpath.match(excludefilestr)
|
|
mod.datastore['PATH'] = strpath
|
|
print_status("Launching #{xref[3]} #{wtype} #{strpath} against #{xref[0]}:#{xref[1]}...")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{name}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
when :WMAP_DIR
|
|
if not node.is_leaf? or node.is_root?
|
|
mod.datastore['PATH'] = strpath
|
|
print_status("Launching #{xref[3]} #{wtype} #{strpath} against #{xref[0]}:#{xref[1]}...")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{name}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Run modules for each request to play with URI with UNIQUE query parameters.
|
|
# WMAP_UNIQUE_QUERY
|
|
#
|
|
idx = 0
|
|
matches5.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins for each request that have a distinct
|
|
# GET/POST URI QUERY string.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
utest_query = {}
|
|
|
|
framework.db.each_request_target_with_query do |req|
|
|
#
|
|
# Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'
|
|
#
|
|
if (utest_query.has_key?(mod.signature(req.path,req.query)) == false)
|
|
#
|
|
# Weird bug req.method doesnt work
|
|
# collides with some method named 'method'
|
|
# column table renamed to 'meth'.
|
|
#
|
|
mod.datastore['METHOD'] = req.meth.upcase
|
|
mod.datastore['PATH'] = req.path
|
|
mod.datastore['QUERY'] = req.query
|
|
mod.datastore['HEADER_SEP'] = STR_HEADER_SEPARATOR
|
|
mod.datastore['HEADERS'] = headers_to_s(req.headers,STR_HEADER_SEPARATOR)
|
|
mod.datastore['COOKIE'] = header(req.headers,"Cookie")
|
|
mod.datastore['DATA'] = req.body
|
|
#
|
|
# TODO: Add method, headers, etc.
|
|
#
|
|
|
|
if wtype == :WMAP_UNIQUE_QUERY
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Unique query tested, actually the value does not matter
|
|
#
|
|
# print_status("#{mod.signature(req.path,req.query)}")
|
|
utest_query[mod.signature(req.path,req.query)]=1
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Run modules for each request to play with URI query parameters.
|
|
# This approach will reduce the complexity of the Tree used before
|
|
# and will make this shotgun implementation much simple.
|
|
# WMAP_QUERY
|
|
#
|
|
idx = 0
|
|
matches2.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins for each request that have a distinct
|
|
# GET/POST URI QUERY string.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
|
|
framework.db.each_request_target_with_query do |req|
|
|
#
|
|
# Weird bug req.method doesnt work
|
|
# collides with some method named 'method'
|
|
# column table renamed to 'meth'.
|
|
#
|
|
mod.datastore['METHOD'] = req.meth.upcase
|
|
mod.datastore['PATH'] = req.path
|
|
mod.datastore['QUERY'] = req.query
|
|
mod.datastore['HEADER_SEP'] = STR_HEADER_SEPARATOR
|
|
mod.datastore['HEADERS'] = headers_to_s(req.headers,STR_HEADER_SEPARATOR)
|
|
mod.datastore['COOKIE'] = header(req.headers,"Cookie")
|
|
mod.datastore['DATA'] = req.body
|
|
|
|
#
|
|
# TODO: Add method, headers, etc.
|
|
#
|
|
|
|
if wtype == :WMAP_QUERY
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Run modules for each request to play with request bodies.
|
|
# WMAP_BODY
|
|
#
|
|
idx = 0
|
|
matches3.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins for each request for all headers
|
|
# This can be improved alot . Later versions
|
|
# Should only tests on unique requests.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
|
|
framework.db.each_request_target_with_body do |req|
|
|
#
|
|
# Weird bug req.method doesnt work
|
|
# collides with some method named 'method'
|
|
# column table renamed to 'meth'.
|
|
#
|
|
mod.datastore['METHOD'] = req.meth.upcase
|
|
mod.datastore['PATH'] = req.path
|
|
mod.datastore['QUERY'] = req.query
|
|
mod.datastore['HEADER_SEP'] = STR_HEADER_SEPARATOR
|
|
mod.datastore['HEADERS'] = headers_to_s(req.headers,STR_HEADER_SEPARATOR)
|
|
mod.datastore['COOKIE'] = header(req.headers,"Cookie")
|
|
mod.datastore['DATA'] = req.body
|
|
#
|
|
# TODO: Add method, headers, etc.
|
|
#
|
|
|
|
if wtype == :WMAP_BODY
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Run modules for each request to play with request headers.
|
|
# WMAP_HEADERS
|
|
#
|
|
idx = 0
|
|
matches4.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins for each request for all headers
|
|
# This can be improved alot . Later versions
|
|
# Should only tests on unique requests.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
|
|
framework.db.each_request_target_with_headers do |req|
|
|
#
|
|
# Weird bug req.method doesnt work
|
|
# collides with some method named 'method'
|
|
# column table renamed to 'meth'.
|
|
#
|
|
mod.datastore['METHOD'] = req.meth.upcase
|
|
mod.datastore['PATH'] = req.path
|
|
mod.datastore['QUERY'] = req.query
|
|
mod.datastore['HEADER_SEP'] = STR_HEADER_SEPARATOR
|
|
mod.datastore['HEADERS'] = headers_to_s(req.headers,STR_HEADER_SEPARATOR)
|
|
mod.datastore['COOKIE'] = header(req.headers,"Cookie")
|
|
mod.datastore['DATA'] = req.body
|
|
#
|
|
# TODO: Add method, headers, etc.
|
|
#
|
|
|
|
if wtype == :WMAP_HEADERS
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
#
|
|
# Handle modules that need to be after all tests, once.
|
|
# Good place to have modules that analize the test results and/or
|
|
# launch exploits.
|
|
# :WMAP_GENERIC
|
|
#
|
|
idx = 0
|
|
matches10.each_key do |xref|
|
|
idx += 1
|
|
|
|
begin
|
|
mod = nil
|
|
|
|
#Carefull with the references on this one
|
|
if ((mod = framework.modules.create(xref[3])) == nil)
|
|
print_status("Failed to initialize #{xref[3]}")
|
|
next
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Loaded #{xref[3]} ...")
|
|
end
|
|
|
|
#
|
|
# The code is just a proof-of-concept and will be expanded in the future
|
|
#
|
|
if (mode & WMAP_EXPL != 0)
|
|
|
|
#
|
|
# For modules to have access to the global datastore
|
|
# i.e. set -g DOMAIN test.com
|
|
#
|
|
self.framework.datastore.each do |gkey,gval|
|
|
mod.datastore[gkey]=gval
|
|
end
|
|
|
|
#
|
|
# Parameters passed in hash xref
|
|
#
|
|
mod.datastore['RHOST'] = xref[0]
|
|
mod.datastore['RHOSTS'] = xref[0]
|
|
mod.datastore['RPORT'] = xref[1].to_s
|
|
mod.datastore['SSL'] = xref[2].to_s
|
|
|
|
#
|
|
# Run the plugins that only need to be
|
|
# launched once.
|
|
#
|
|
|
|
wtype = mod.wmap_type
|
|
|
|
if wtype == :WMAP_GENERIC
|
|
print_status("Launching #{xref[3]} #{wtype} against #{xref[0]}:#{xref[1]}")
|
|
|
|
# To run check function for modules that are exploits
|
|
if mod.respond_to?("check") and WMAP_CHECK
|
|
begin
|
|
session = mod.check_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during check launch from #{xref[3]}: #{$!}")
|
|
end
|
|
else
|
|
|
|
begin
|
|
session = mod.run_simple(
|
|
'LocalInput' => driver.input,
|
|
'LocalOutput' => driver.output,
|
|
'RunAsJob' => false)
|
|
rescue ::Exception
|
|
print_status(" >> Exception during launch from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Exception
|
|
print_status(" >> Exception from #{xref[3]}: #{$!}")
|
|
end
|
|
end
|
|
|
|
if (mode & WMAP_SHOW != 0)
|
|
print_status("Analysis completed in #{(Time.now.to_f - stamp)} seconds.")
|
|
print_status("Done.")
|
|
end
|
|
|
|
# EOM
|
|
end
|
|
|
|
#
|
|
# Run msf proxy
|
|
#
|
|
|
|
def cmd_wmap_proxy(*args)
|
|
cmdline = PROXY_CMDLINE
|
|
proxyopts = PROXY_DEFAULTOPTS + " " + args.join(" ")
|
|
|
|
tpid = 0
|
|
proxypid = Process.fork
|
|
if proxypid.nil?
|
|
exec cmdline + " " + proxyopts
|
|
else
|
|
tpid = proxypid
|
|
Process.detach(proxypid)
|
|
end
|
|
print_status("Cmd: #{cmdline}")
|
|
print_status("Options: #{proxyopts}")
|
|
print_status("Executing proxy. pid: #{tpid}")
|
|
print_status("Done.")
|
|
end
|
|
|
|
#
|
|
# Run msf crawler
|
|
#
|
|
|
|
def cmd_wmap_crawl(*args)
|
|
|
|
cmdline = CRAWLER_CMDLINE
|
|
crawlopts = CRAWLER_DEFAULTOPTS + " " + args.join(" ")
|
|
|
|
tpid = 0
|
|
crawlpid = Process.fork
|
|
if crawlpid.nil?
|
|
exec cmdline + " " + crawlopts
|
|
else
|
|
tpid = crawlpid
|
|
Process.detach(crawlpid)
|
|
end
|
|
print_status("Cmd: #{cmdline}")
|
|
print_status("Options: #{crawlopts}")
|
|
print_status("Crawler. pid: #{tpid}")
|
|
print_status("Done.")
|
|
end
|
|
|
|
|
|
#
|
|
# Load website structure into a tree
|
|
#
|
|
|
|
def load_tree
|
|
wtree = Tree.new("ROOT_TREE")
|
|
|
|
if selected_host == nil
|
|
print_error("Target not selected")
|
|
else
|
|
framework.db.each_request_target do |req|
|
|
tarray = req.path.to_s.split(WMAP_PATH)
|
|
tarray.delete("")
|
|
tpath = Pathname.new(WMAP_PATH)
|
|
|
|
tarray.each do |df|
|
|
wtree.add_at_path(tpath.to_s,df)
|
|
tpath = tpath + Pathname.new(df.to_s)
|
|
end
|
|
end
|
|
end
|
|
return wtree
|
|
end
|
|
|
|
#
|
|
# Print Tree structure. Ugly
|
|
#
|
|
|
|
def print_tree(tree)
|
|
if tree.is_leaf? and tree.depth > 0
|
|
print_line(("|\t"*(tree.depth-1))+"+------"+tree.name)
|
|
else
|
|
print_line(("|\t"*tree.depth)+tree.name)
|
|
end
|
|
tree.children.each_pair do |name,child|
|
|
print_tree(child)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Method to parse URIs Regex RFC3986
|
|
#
|
|
def uri_parse(uri)
|
|
if uri == ''
|
|
print_error("URI required")
|
|
return
|
|
end
|
|
|
|
regexstr = '^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?'
|
|
|
|
regexurl = Regexp.new(regexstr, false)
|
|
ret = regexurl.match(uri)
|
|
|
|
return ret
|
|
end
|
|
|
|
#
|
|
# Method to transfor headers to string
|
|
#
|
|
def headers_to_s(nheaders,hsep)
|
|
hstr = ""
|
|
nheaders.each_line do |lh|
|
|
if hstr.empty?
|
|
hstr = lh
|
|
else
|
|
hstr = lh + hsep + hstr
|
|
end
|
|
end
|
|
return hstr
|
|
end
|
|
|
|
#
|
|
# Method to grab a specific header
|
|
#
|
|
def header(nheaders,hname)
|
|
nheaders.each_line do |lh|
|
|
n,v = lh.split(": ")
|
|
if n == hname
|
|
return v
|
|
end
|
|
end
|
|
return hstr
|
|
end
|
|
|
|
|
|
#
|
|
# Selected target
|
|
#
|
|
def selected_host
|
|
framework.db.selected_host
|
|
end
|
|
|
|
def selected_port
|
|
framework.db.selected_port
|
|
end
|
|
|
|
def selected_ssl
|
|
framework.db.selected_ssl
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
def initialize(framework, opts)
|
|
super
|
|
add_console_dispatcher(WmapCommandDispatcher)
|
|
print_status("#{WMAPBanner}")
|
|
end
|
|
|
|
def cleanup
|
|
remove_console_dispatcher('Wmap')
|
|
end
|
|
|
|
def name
|
|
"wmap"
|
|
end
|
|
|
|
def desc
|
|
"Web assessment plugin"
|
|
end
|
|
|
|
protected
|
|
|
|
end
|
|
end
|