metasploit-framework/modules/auxiliary/scanner/http/soap_xml.rb

206 lines
6.2 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < 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))
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 milliseconds 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 = %w(
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
)
verbs << 'delete' if datastore['VERB_DELETE']
nouns = %w(
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
)
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)
print_status("Starting scan with #{datastore['SLEEP']}ms delay between requests")
verbs.each do |v|
nouns.each do |n|
begin
data_parts = []
data_parts << '<?xml version="1.0" encoding="utf-8"?>'
data_parts << "<soap:Envelope xmlns:xsi=\"#{datastore['XMLINSTANCE']}\" xmlns:xsd=\"#{datastore['XMLSCHEMA']}\" xmlns:soap=\"#{datastore['XMLSOAP']}\">"
data_parts << '<soap:Body>'
data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">"
data_parts << "</#{v}#{n}>"
data_parts << '</soap:Body>'
data_parts << '</soap:Envelope>'
data_parts << nil
data_parts << nil
data = data_parts.join("\r\n")
uri = normalize_uri(datastore['PATH'])
uri += '/' unless uri =~ /^\/$/
uri += v + n
vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}")
res = send_request_raw(
{
'uri' => uri,
'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 reject_regexen.any? { |r| res.body =~ r }
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
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
print_error(e.message)
ensure
Rex.sleep(sleep_time)
end
end
end
end
def sleep_time
datastore['SLEEP'] / 1000.0
end
end