1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-12 11:52:01 +01:00

Land #3526, @jhart-r7's soap_xml scanner cleanup

This commit is contained in:
jvazquez-r7 2014-09-12 13:29:52 -05:00
commit 373861abb0
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83

View File

@ -17,183 +17,189 @@ class Metasploit3 < Msf::Auxiliary
def initialize(info = {})
super(update_info(info,
'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner',
'Description' => %q{
'Description' => %q(
This module attempts to brute force SOAP/XML requests to uncover
hidden methods.
},
'Author' => [ 'patrick' ],
),
'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 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'])
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 = [
'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']
if (datastore['VERB_DELETE'])
verbs << 'delete'
end
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
)
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)
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|
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 << '<?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 << '<soap:Body>'
data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">"
data_parts << "</#{v}#{n}>"
data_parts << "</soap:Body>"
data_parts << "</soap:Envelope>"
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'])
vprint_status("Sending request #{uri}/#{v}#{n} to #{wmap_target_host}:#{datastore['RPORT']}")
uri += '/' unless uri =~ /^\/$/
uri += v + n
res = send_request_raw({
'uri' => uri + '/' + 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)
vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}")
if (res && !(res.body.empty?))
if ((not reject_regexen.select { |r| res.body =~ r }.empty?))
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/)
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)
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}."
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('The HTML content follows:')
print_status(res.body + "\r\n")
end
end
end
select(nil, nil, nil, datastore['SLEEP']) if (datastore['SLEEP'] > 0)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
print_error(e.message)
ensure
Rex.sleep(sleep_time)
end
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
vprint_error(e.message)
end
end
def sleep_time
datastore['SLEEP'] / 1000.0
end
end