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:
commit
373861abb0
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user