auxiliary/scanner/http/soap_xml cleanup

This:

* Corrects Ruby style (most) everywhere
* Uses Rex's sleep, converts to milliseconds -- seconds are too granular
* Moves begin/rescue inside nested verb+noun loop
* Prints errors even if not in verbose mode
* Corrects URI construction when PATH ends with /
bug/bundler_fix
Jon Hart 2014-07-14 15:14:10 -07:00
parent 6e3739a220
commit 5c61c09c6b
1 changed files with 133 additions and 128 deletions

View File

@ -17,183 +17,188 @@ class Metasploit3 < Msf::Auxiliary
def initialize(info = {}) def initialize(info = {})
super(update_info(info, super(update_info(info,
'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner', 'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner',
'Description' => %q{ 'Description' => %q(
This module attempts to brute force SOAP/XML requests to uncover This module attempts to brute force SOAP/XML requests to uncover
hidden methods. hidden methods.
}, ),
'Author' => [ 'patrick' ], 'Author' => ['patrick'],
'License' => MSF_LICENSE)) 'License' => MSF_LICENSE))
register_options( register_options(
[ [
OptString.new('PATH', [ true, "The path to test", '/']), OptString.new('PATH', [true, 'The path to test', '/']),
OptString.new('XMLNAMESPACE', [ true, "XML Web Service Namespace", 'http://tempuri.org/']), 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('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('XMLSCHEMA', [true, 'XML Schema', 'http://www.w3.org/2001/XMLSchema']),
OptString.new('XMLSOAP', [ true, "XML SOAP", 'http://schemas.xmlsoap.org/soap/envelope/']), 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']), 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 ]), OptInt.new('SLEEP', [true, 'Sleep this many milliseconds between requests', 0]),
OptBool.new('DISPLAYHTML', [ true, "Display HTML response", false ]), OptBool.new('DISPLAYHTML', [true, 'Display HTML response', false]),
OptBool.new('SSL', [ true, "Use SSL", false ]), OptBool.new('SSL', [true, 'Use SSL', false]),
OptBool.new('VERB_DELETE', [ false, "Enable 'delete' verb", 'false']) OptBool.new('VERB_DELETE', [false, 'Enable DELETE verb', false])
], self.class) ], self.class)
end end
# Fingerprint a single host # Fingerprint a single host
def run_host(ip) 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 = [ verbs << 'delete' if datastore['VERB_DELETE']
'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']) nouns = %w(
verbs << 'delete' password
end 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 vhost = datastore['VHOST'] || wmap_target_host || ip
# regular expressions for common rejection messages # regular expressions for common rejection messages
reject_regexen = [] reject_regexen = []
reject_regexen << Regexp.new("method \\S+ is not valid", true) reject_regexen << Regexp.new('method \\S+ is not valid', true)
reject_regexen << Regexp.new("Method \\S+ not implemented", 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('unable to resolve WSDL method name', true)
begin verbs.each do |v|
verbs.each do |v| nouns.each do |n|
nouns.each do |n| begin
data_parts = [] 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: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} xmlns=\"#{datastore['XMLNAMESPACE']}\">"
data_parts << "</#{v}#{n}>" data_parts << "</#{v}#{n}>"
data_parts << "</soap:Body>" data_parts << '</soap:Body>'
data_parts << "</soap:Envelope>" data_parts << '</soap:Envelope>'
data_parts << nil data_parts << nil
data_parts << nil data_parts << nil
data = data_parts.join("\r\n") data = data_parts.join("\r\n")
uri = normalize_uri(datastore['PATH']) 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({ vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}")
'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)
if (res && !(res.body.empty?)) res = send_request_raw(
if ((not reject_regexen.select { |r| res.body =~ r }.empty?)) {
'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}.") 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}.") print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.")
res.message =~ /was not the expected type\s\'([^']+)'/ res.message =~ /was not the expected type\s\'([^']+)'/
print_status("Set CONTENTTYPE to \"#{$1}\"") print_status("Set CONTENTTYPE to \"#{$1}\"")
return false 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.") print_status("Server #{wmap_target_host}:#{datastore['RPORT']} returned HTTP 404 for #{datastore['PATH']}. Use a different one.")
return false return false
else else
print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.")
## Add Report ## Add Report
report_note( report_note(
:host => ip, host: ip,
:proto => 'tcp', proto: 'tcp',
:sname => (ssl ? 'https' : 'http'), sname: (ssl ? 'https' : 'http'),
:port => rport, port: rport,
:type => "SOAPAction: #{v}#{n}", type: "SOAPAction: #{v}#{n}",
:data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." data: "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}."
) )
if datastore['DISPLAYHTML'] if datastore['DISPLAYHTML']
print_status("The HTML content follows:") print_status('The HTML content follows:')
print_status(res.body + "\r\n") print_status(res.body + "\r\n")
end end
end 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
end end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
vprint_error(e.message)
end end
end end
def sleep_time
datastore['SLEEP'] / 1000.0
end
end end