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
parent
6e3739a220
commit
5c61c09c6b
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue