diff --git a/data/sql/sqlite.sql b/data/sql/sqlite.sql index 0e30beb333..359be8c507 100644 --- a/data/sql/sqlite.sql +++ b/data/sql/sqlite.sql @@ -57,3 +57,28 @@ create table notes ( 'ntype' VARCHAR(512), 'data' TEXT ); + +drop table requests; +create table requests ( +'host' VARCHAR(20), +'port' INTEGER, +'ssl' INTEGER, +'meth' VARCHAR(20), +'path' BLOB, +'headers' BLOB, +'query' BLOB, +'body' BLOB, +'respcode' VARCHAR(5), +'resphead' BLOB, +'response' BLOB, +'created' TIMESTAMP +); + +drop table targets; +create table targets ( +'id' INTEGER PRIMARY KEY NOT NULL, +'host' VARCHAR(20), +'port' INTEGER, +'ssl' INTEGER, +'selected' INTEGER +); diff --git a/data/wmap/sql/sqlite.sql b/data/wmap/sql/sqlite.sql deleted file mode 100644 index 52f4b48687..0000000000 --- a/data/wmap/sql/sqlite.sql +++ /dev/null @@ -1,34 +0,0 @@ -drop table requests; -create table requests ( -'host' VARCHAR(20), -'port' INTEGER, -'ssl' INTEGER, -'meth' VARCHAR(20), -'path' BLOB, -'headers' BLOB, -'query' BLOB, -'body' BLOB, -'respcode' VARCHAR(5), -'resphead' BLOB, -'response' BLOB, -'created' TIMESTAMP -); - - -drop table targets; -create table targets ( -'id' INTEGER PRIMARY KEY NOT NULL, -'host' VARCHAR(20), -'port' INTEGER, -'ssl' INTEGER, -'selected' INTEGER -); - -drop table notes; -create table notes ( -'id' INTEGER PRIMARY KEY NOT NULL, -'created' TIMESTAMP, -'host_id' INTEGER, -'ntype' VARCHAR(512), -'data' TEXT -); diff --git a/documentation/wmap.txt b/documentation/wmap.txt index 5354292475..460b99c3f1 100644 --- a/documentation/wmap.txt +++ b/documentation/wmap.txt @@ -1,4 +1,4 @@ -=[ WMAP v0.1 ET LoWNOISE et[]metasploit.com +=[ WMAP v0.2 ET LoWNOISE et[]metasploit.com --------------------------------------------------------------------------- "Metasploit goes Web" ,H D Moore. @@ -16,11 +16,11 @@ or spider for data capture and manipulation. In the WMAP design, the attack proxy acts as a data gathering tool. All traffic between the client(s) (i.e. favorite browser and/or spider) will be -stored in the WMAP database. (See figure.) +stored in the MSF database. (See figure.) [CLIENT] ----- [ATTACK PROXY] ----- [TARGET] | ^ - [WMAP DB] | + [METASPLOIT DB] | | | [MSF 3 - WMAP SCANNER] | [MSF 3 - WMAP MODULES] -----+ @@ -35,7 +35,7 @@ As you may see this simple architecture allows you to have different distributed clients and even different proxies all storing data to the central repository. Remember everything is based on Metasploit, the test modules are implemented as auxiliary modules and they can interact with any -other MSF component including exploits and plugins. +other MSF component including the database, exploits and plugins. =[ WMAP Modules. @@ -67,6 +67,9 @@ WMAPScanUniqueQuery - Runs for every unique query found in each request to the WMAPScanQuery - Runs for every query found in each request to the target WMAPScanBody - Runs for every Body found in each request to the target WMAPScanHeaders - Runs for every Header found in each request to the target +WMAPScanGeneric - Modules to be run after all tests complete. Good place to + perform passive analysis of responses, analysis of test + results to launch other modules (i.e. exploits). =[ Simple example. @@ -81,16 +84,16 @@ The following are the basic steps for testing a web server/app using WMAP: ratproxy (ratproxy_wmap.diff applied) basic example: - $ ./ratproxy -v metasploit3/data/wmap/ -b wmap_sqlite3.db + $ ./ratproxy -v /Users/et/.msf3/ -b sqlite3.db ratproxy version 1.51-beta by [*] Proxy configured successfully. Have fun, and please do not be evil. [+] Accepting connections on port 8080/tcp (local only)... - NOTE: If you want to use a different database than 'wmap_sqlite3.db' - stored in /data/wmap/ or destroy it. You have to load the WMAP plugin - (go to step 4) and use wmap command 'wmap_create ': + NOTE: If you want to use a different database than the default MSF database + or was destroyed. You have to create again the database with the 'db_create' + command. 'db_create ': - msf > wmap_create /dir/target_test.db + msf > db_create /dir/target_test.db [*] Creating a new database instance... [*] Successfully connected to the database [*] File: /dir/target_test.db @@ -98,7 +101,7 @@ The following are the basic steps for testing a web server/app using WMAP: 3. Browse the target by running your favorite spider/crawler/browser etc. NOTE: Dont forget to configure the proxy; - 4. In Metasploit load the WMAP plugin; + 4. In Metasploit load the db_ plugin; $ ./msfconsole @@ -114,15 +117,19 @@ The following are the basic steps for testing a web server/app using WMAP: + -- --=[ 20 encoders - 6 nops =[ 74 aux + msf > load db_sqlite3 msf > load db_wmap - [*] =[ WMAP v0.1 - ET LoWNOISE - [*] Successfully loaded plugin: db_wmap_sqlite3 + [*] =[ WMAP v0.2 - ET LoWNOISE + [*] Successfully loaded plugin: db_wmap 5. Connect to the WMAP database; msf > wmap_connect [*] Successfully connected to the wmap database - [*] File: /metasploit3/data/wmap/wmap_sqlite3.db + [*] File: /users/et/.msf3/sqlite3.db + + + msf > wmap_targets -r [*] Reloading targets... [*] Added. metasploit.com 80 0 [*] Added. target.com 80 0 @@ -155,15 +162,15 @@ The following are the basic steps for testing a web server/app using WMAP: [*] Website structure [*] target.com:80 SSL:0 ROOT_TREE - index.asp - images - logo.gif - login.asp - menu - menu.asp - logout.asp - help.asp - [*] Done. + +------ index.asp + | images + | +------ logo.gif + +------ login.asp + | menu + | +------ menu.asp + | +------ logout.asp + | +------ help.asp + [*] Done. 9. List loaded modules; diff --git a/lib/msf/core/auxiliary/wmapmodule.rb b/lib/msf/core/auxiliary/wmapmodule.rb index 533d465333..c165f942ba 100644 --- a/lib/msf/core/auxiliary/wmapmodule.rb +++ b/lib/msf/core/auxiliary/wmapmodule.rb @@ -184,4 +184,18 @@ module Auxiliary::WMAPScanHeaders end end +### +# +# This module provides methods for WMAP Generic Scanner modules +# +### + +module Auxiliary::WMAPScanGeneric + include Auxiliary::WMAPModule + + def wmap_type + :WMAP_GENERIC + end +end + end diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 1524304b68..499ac9a7a4 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -372,7 +372,180 @@ class DBManager "vulns_refs.vuln_id = vulns.id" ) end + + # + # WMAP + # Support methods + # + + # + # WMAP + # Selected host + # + def selected_host + selhost = Target.find(:first, :conditions => ["selected > 0"] ) + if selhost + return selhost.host + else + return + end + end + + # + # WMAP + # Selected port + # + def selected_port + Target.find(:first, :conditions => ["selected > 0"] ).port + end + # + # WMAP + # Selected ssl + # + def selected_ssl + Target.find(:first, :conditions => ["selected > 0"] ).ssl + end + + # + # WMAP + # This method iterates the requests table identifiying possible targets + # This method wiil be remove on second phase of db merging. + # + def each_distinct_target(&block) + request_distinct_targets.each do |target| + block.call(target) + end + end + + # + # WMAP + # This method returns a list of all possible targets available in requests + # This method wiil be remove on second phase of db merging. + # + def request_distinct_targets + Request.find(:all, :select => 'DISTINCT host,port,ssl') + end + + # + # WMAP + # This method iterates the requests table returning a list of all requests of a specific target + # + def each_request_target_with_path(&block) + target_requests('AND requests.path IS NOT NULL').each do |req| + block.call(req) + end + end + + # + # WMAP + # This method iterates the requests table returning a list of all requests of a specific target + # + def each_request_target_with_body(&block) + target_requests('AND requests.body IS NOT NULL').each do |req| + block.call(req) + end + end + + # + # WMAP + # This method iterates the requests table returning a list of all requests of a specific target + # + def each_request_target_with_headers(&block) + target_requests('AND requests.headers IS NOT NULL').each do |req| + block.call(req) + end + end + + # + # WMAP + # This method iterates the requests table returning a list of all requests of a specific target + # + def each_request_target(&block) + target_requests('').each do |req| + block.call(req) + end + end + + # + # WMAP + # This method returns a list of all requests from target + # + def target_requests(extra_condition) + Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ? #{extra_condition}",selected_host,selected_port]) + end + + # + # WMAP + # This method iterates the requests table calling the supplied block with the + # request instance of each entry. + # + def each_request(&block) + requests.each do |request| + block.call(request) + end + end + + # + # WMAP + # This methods returns a list of all targets in the database + # + def requests + Request.find(:all) + end + + # + # WMAP + # This method iterates the targets table calling the supplied block with the + # target instance of each entry. + # + def each_target(&block) + targets.each do |target| + block.call(target) + end + end + + # + # WMAP + # This methods returns a list of all targets in the database + # + def targets + Target.find(:all) + end + + # + # WMAP + # This methods deletes all targets from targets table in the database + # + def delete_all_targets + Target.delete_all + end + + # + # WMAP + # Find a target matching this id + # + def get_target(id) + target = Target.find(:first, :conditions => [ "id = ?", id]) + return target + end + + # + # WMAP + # Create a target + # + def create_target(host,port,ssl,sel) + tar = Target.create( + :host => host, + :port => port, + :ssl => ssl, + :selected => sel + ) + tar.save + #framework.events.on_db_target(context, rec) + end + + end diff --git a/lib/msf/core/db_objects.rb b/lib/msf/core/db_objects.rb index 9a14bcaba1..5bfaf37a7f 100644 --- a/lib/msf/core/db_objects.rb +++ b/lib/msf/core/db_objects.rb @@ -95,5 +95,19 @@ class Note < ActiveRecord::Base end end + +# WMAP Request object definition +class Request < ::ActiveRecord::Base + include DBSave + # Magic. +end + +# WMAP Target object definition +class Target < ::ActiveRecord::Base + include DBSave + # Magic. +end + + end end diff --git a/lib/msf/ui/console/command_dispatcher/wmap.rb b/lib/msf/ui/console/command_dispatcher/wmap.rb index 269b41cafc..375d163335 100644 --- a/lib/msf/ui/console/command_dispatcher/wmap.rb +++ b/lib/msf/ui/console/command_dispatcher/wmap.rb @@ -15,21 +15,7 @@ module Wmap # # MSF WMAP Web scanner ET LowNOISE # et[cron]cyberspace.org - # - - - # - # Ugly fix for wmap tables. - # Reason: Not to mess with DbManager/DBObjects. - # - - class Request < ::ActiveRecord::Base - # Magic. - end - - class Target < ::ActiveRecord::Base - # Magic. - end + # # # Constants @@ -52,7 +38,6 @@ module Wmap def commands { "wmap_website" => "List website structure", -# "wmap_vulns" => "List all vulnerabilities in the database", "wmap_targets" => "List all targets in the database", "wmap_run" => "Automatically test/exploit everything", } @@ -69,13 +54,6 @@ module Wmap print_status("Done.") end - def cmd_wmap_vulns(*args) - framework.db.each_vuln do |vuln| - reflist = vuln.refs.map { |r| r.name } - print_status("Time: #{vuln.created} Vuln: host=#{vuln.host.address} port=#{vuln.service.port} proto=#{vuln.service.proto} name=#{vuln.name} refs=#{reflist.join(',')}") - end - end - def cmd_wmap_targets(*args) args.push("-h") if args.length == 0 @@ -84,7 +62,7 @@ module Wmap when '-p' print_status(" Id. Host\t\t\t\t\tPort\tSSL") - Target.find(:all).each do |tgt| + framework.db.each_target do |tgt| if tgt.ssl == 1 usessl = "[*]" else @@ -104,18 +82,17 @@ module Wmap end print_status("Done.") when '-r' - Target.delete_all - Request.find(:all, :select => 'DISTINCT host,port,ssl').each do |req| - target = Target.create(:host => req.host, :port => req.port, :ssl => req.ssl, :selected => 0) - target.save + framework.db.delete_all_targets + framework.db.each_distinct_target do |req| + framework.db.create_target(req.host, req.port, req.ssl, 0) print_status("Added. #{req.host} #{req.port} #{req.ssl}") end when '-s' - Target.find(:all).each do |tgt| + framework.db.each_target do |tgt| tgt.selected = 0 tgt.save end - seltgt = Target.find(:first, :conditions => ["id = ?", args.shift]) + seltgt = framework.db.get_target(args.shift) if seltgt == nil print_error("Target id not found.") else @@ -185,6 +162,9 @@ module Wmap # WMAP_UNIQUE_QUERY matches5 = {} + # WMAP_GENERIC + matches10 = {} + [ [ framework.auxiliary, 'auxiliary' ] ].each do |mtype| @@ -211,11 +191,13 @@ module Wmap when :WMAP_HEADERS matches4[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true when :WMAP_UNIQUE_QUERY - matches5[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true + matches5[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true + when :WMAP_GENERIC + matches10[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true when :WMAP_DIR, :WMAP_FILE matches[[selected_host,selected_port,selected_ssl,mtype[1]+'/'+n]]=true else - # To add oter types in the future. (i.e EXPLOIT) + # Black Hole end end end @@ -454,7 +436,7 @@ module Wmap utest_query = {} - Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ? AND requests.path IS NOT NULL",selected_host,selected_port]).each do |req| + framework.db.each_request_target_with_path do |req| # # Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn' # @@ -499,7 +481,6 @@ module Wmap end end - # # Run modules for each request to play with URI query parameters. # This approach will reduce the complexity of the Tree used before @@ -551,7 +532,7 @@ module Wmap wtype = mod.wmap_type - Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ? AND requests.path IS NOT NULL",selected_host,selected_port]).each do |req| + framework.db.each_request_target_with_path do |req| # # Weird bug req.method doesnt work # collides with some method named 'method' @@ -636,7 +617,7 @@ module Wmap wtype = mod.wmap_type - Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ? AND requests.body IS NOT NULL",selected_host,selected_port]).each do |req| + framework.db.each_request_target_with_body do |req| # # Weird bug req.method doesnt work # collides with some method named 'method' @@ -721,7 +702,7 @@ module Wmap wtype = mod.wmap_type - Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ? AND requests.headers IS NOT NULL",selected_host,selected_port]).each do |req| + framework.db.each_request_target_with_headers do |req| # # Weird bug req.method doesnt work # collides with some method named 'method' @@ -755,6 +736,75 @@ module Wmap print_status(" >> Exception from #{xref[3]}: #{$!.to_s}") 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) + + # + # Parameters passed in hash xref + # + mod.datastore['RHOSTS'] = xref[0] + mod.datastore['RPORT'] = xref[1].to_s + mod.datastore['SSL'] = xref[2].to_s + + # + # 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 + + # + # 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_s}") + + begin + session = mod.run_simple( + 'LocalInput' => driver.input, + 'LocalOutput' => driver.output, + 'RunAsJob' => false) + rescue ::Exception + print_status(" >> Exception during launch from #{xref[3]}: #{$!.to_s}") + end + end + end + + rescue ::Exception + print_status(" >> Exception from #{xref[3]}: #{$!.to_s}") + end + end if (mode & WMAP_SHOW != 0) print_status("Analysis completed in #{(Time.now.to_f - stamp).to_s} seconds.") @@ -765,28 +815,6 @@ module Wmap end - # - # Wmap support methods - # - - def selected_host - selhost = Target.find(:first, :conditions => ["selected > 0"] ) - if selhost - return selhost.host - else - return - end - end - - def selected_port - Target.find(:first, :conditions => ["selected > 0"] ).port - end - - def selected_ssl - Target.find(:first, :conditions => ["selected > 0"] ).ssl - end - - # # Load website structure into a tree # @@ -798,7 +826,7 @@ module Wmap print_error("Target not selected") else - Request.find(:all, :conditions => ["requests.host = ? AND requests.port = ?",selected_host,selected_port]).each do |req| + framework.db.each_request_target do |req| tarray = req.path.to_s.split(WMAP_PATH) tarray.delete("") tpath = Pathname.new(WMAP_PATH) @@ -817,12 +845,32 @@ module Wmap # def print_tree(tree) - print_line(("\t"*tree.depth)+tree.name) + if tree.is_leaf? + 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) + print_tree(child) end 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 end end diff --git a/plugins/db_wmap.rb b/plugins/db_wmap.rb index c9b05d0feb..fc00567f53 100644 --- a/plugins/db_wmap.rb +++ b/plugins/db_wmap.rb @@ -1,4 +1,5 @@ require 'fileutils' +require 'msf/ui/console/command_dispatcher/db' require 'msf/ui/console/command_dispatcher/wmap' require 'rubygems' require 'sqlite3' @@ -10,11 +11,6 @@ module Msf # This class intializes the database db with a shiny new # SQLite3 database instance. # -# For wmap purposes this is a full copy of db_sqlite.rb plugin -# with minor modification poinitng to a different -# command dispatcher and a diferent sql file in data/wmap -# directory. -# # ET LoWNOISE 08 # ### @@ -39,106 +35,9 @@ class Plugin::DBWmap < Msf::Plugin # def commands { - "wmap_connect" => "Connect to an existing database ( /path/to/db )", - "wmap_disconnect" => "Disconnect from the current database instance", - "wmap_create" => "Create a brand new database ( /path/to/db )", - "wmap_destroy" => "Drop an existing database ( /path/to/db )" + } - end - - - # - # Disconnect from the current SQLite instance - # - def cmd_wmap_disconnect(*args) - if (framework.db) - framework.db.disconnect() - driver.remove_dispatcher('Database Backend') - end - end - - # - # Connect to an existing SQLite database - # - def cmd_wmap_connect(*args) - - info = parse_db_uri(args[0]) - opts = { 'adapter' => 'sqlite3' } - - opts['dbfile'] = info[:path] - - if (not File.exists?(opts['dbfile'])) - print_status("The specified database does not exist") - return - end - - if (not framework.db.connect(opts)) - raise PluginLoadError.new("Failed to connect to the database") - end - - driver.append_dispatcher(WmapDatabaseCommandDispatcher) - - print_status("Successfully connected to the wmap database") - print_status("File: #{opts['dbfile']}") - - print_status("Reloading targets...") - driver.run_single("wmap_targets -r") - end - - # - # Create a new SQLite database instance - # - def cmd_wmap_create(*args) - cmd_wmap_disconnect() - - info = parse_db_uri(args[0]) - opts = { 'adapter' => 'sqlite3' } - - opts['dbfile'] = info[:path] - - sql = File.join(Msf::Config.install_root, "data", "wmap", "sql", "sqlite.sql") - - if (File.exists?(opts['dbfile'])) - print_status("The specified database already exists, connecting") - else - - print_status("Creating a new database instance...") - - db = SQLite3::Database.new(opts['dbfile']) - File.read(sql).split(";").each do |line| - begin - db.execute(line.strip) - rescue ::SQLite3::SQLException, ::SQLite3::MisuseException - end - end - db.close - end - - - if (not framework.db.connect(opts)) - raise PluginLoadError.new("Failed to connect to the database") - end - - driver.append_dispatcher(WmapDatabaseCommandDispatcher) - - print_status("Successfully connected to the database") - print_status("File: #{opts['dbfile']}") - end - - # - # Drop an existing database - # - def cmd_wmap_destroy(*args) - cmd_wmap_disconnect() - info = parse_db_uri(args[0]) - File.unlink(info[:path]) - end - - def parse_db_uri(path) - res = {} - res[:path] = path || File.join(Msf::Config.install_root, "data", "wmap", "wmap_sqlite3.db") - res - end + end end # @@ -157,9 +56,13 @@ class Plugin::DBWmap < Msf::Plugin def initialize(framework, opts) super + + #add_console_dispatcher(WmapDatabaseCommandDispatcher) + add_console_dispatcher(WmapSQLiteCommandDispatcher) + add_console_dispatcher(WmapDatabaseCommandDispatcher) - print_status("=[ WMAP v0.1 - ET LoWNOISE") + print_status("=[ WMAP v0.2 - ET LoWNOISE") end @@ -172,7 +75,7 @@ class Plugin::DBWmap < Msf::Plugin # This method returns a short, friendly name for the plugin. # def name - "db_wmap_sqlite3" + "db_wmap" end #