## # $Id$ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Auxiliary::WmapScanDir # Scanner mixin should be near last include Msf::Auxiliary::Scanner def initialize(info = {}) super(update_info(info, 'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner', 'Description' => %q{ This module attempts to brute force SOAP/XML requests to uncover hidden methods. }, 'Author' => [ 'patrick' ], 'License' => MSF_LICENSE, 'Version' => '$Revision$')) register_options( [ OptString.new('PATH', [ true, "The path to test", '/']), OptString.new('XMLNAMESPACE', [ true, "XML Web Service Namespace", 'http://tempuri.org/']), OptString.new('XMLINSTANCE', [ true, "XML Schema Instance", 'http://www.w3.org/2001/XMLSchema-instance']), OptString.new('XMLSCHEMA', [ true, "XML Schema", 'http://www.w3.org/2001/XMLSchema']), OptString.new('XMLSOAP', [ true, "XML SOAP", 'http://schemas.xmlsoap.org/soap/envelope/']), OptString.new('CONTENTTYPE', [ true, "The HTTP Content-Type Header", 'application/x-www-form-urlencoded']), OptInt.new('SLEEP', [true, "Sleep this many seconds between requests", 0 ]), OptBool.new('DISPLAYHTML', [ true, "Display HTML response", false ]), OptBool.new('SSL', [ true, "Use SSL", false ]), OptBool.new('VERB_DELETE', [ false, "Enable 'delete' verb", 'false']) ], self.class) end # Fingerprint a single host def run_host(ip) verbs = [ 'get', 'active', 'activate', 'create', 'change', 'set', 'put', 'do', 'go', 'resolve', 'start', 'recover', 'initiate', 'negotiate', 'define', 'stop', 'begin', 'end', 'manage', 'administer', 'modify', 'register', 'log', 'add', 'list', 'query', ] if (datastore['VERB_DELETE']) verbs << 'delete' end nouns = [ 'password', 'task', 'tasks', 'pass', 'administration', 'account', 'accounts', 'admin', 'login', 'logins', 'token', 'tokens', 'credential', 'credentials', 'key', 'keys', 'guid', 'message', 'messages', 'user', 'users', 'username', 'usernames', 'load', 'list', 'name', 'names', 'file', 'files', 'path', 'paths', 'directory', 'directories', 'configuration', 'configurations', 'config', 'configs', 'setting', 'settings', 'registry', 'on', 'off', ] target_port = datastore['RPORT'] vhost = datastore['VHOST'] || wmap_target_host || ip # regular expressions for common rejection messages reject_regexen = [] reject_regexen << Regexp.new("method \\S+ is not valid", true) reject_regexen << Regexp.new("Method \\S+ not implemented", true) reject_regexen << Regexp.new("unable to resolve WSDL method name", true) begin verbs.each do |v| nouns.each do |n| data_parts = [] data_parts << "" data_parts << "" data_parts << "" data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">" data_parts << "" data_parts << "" data_parts << "" data_parts << nil data_parts << nil data = data_parts.join("\r\n") vprint_status("Sending request #{datastore['PATH']}/#{v}#{n} to #{wmap_target_host}:#{datastore['RPORT']}") res = send_request_raw({ 'uri' => datastore['PATH'] + '/' + v + n, 'method' => 'POST', 'vhost' => vhost, 'data' => data, 'headers' => { 'Content-Length' => data.length, 'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"', 'Expect' => '100-continue', 'Content-Type' => datastore['CONTENTTYPE'], } }, 15) if (res && !(res.body.empty?)) if ((not reject_regexen.select { |r| res.body =~ r }.empty?)) print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") elsif (res.message =~ /Cannot process the message because the content type/) print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.") res.message =~ /was not the expected type\s\'([^']+)'/ print_status("Set CONTENTTYPE to \"#{$1}\"") return false elsif (res.code == 404) print_status("Server #{wmap_target_host}:#{datastore['RPORT']} returned HTTP 404 for #{datastore['PATH']}. Use a different one.") return false else print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") ## Add Report report_note( :host => ip, :proto => 'tcp', :sname => (ssl ? 'https' : 'http'), :port => rport, :type => "SOAPAction: #{v}#{n}", :data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." ) if datastore['DISPLAYHTML'] print_status("The HTML content follows:") print_status(res.body + "\r\n") end end end select(nil, nil, nil, datastore['SLEEP']) if (datastore['SLEEP'] > 0) end end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e vprint_error(e.message) end end end