Skip to content

Commit 5c61c09

Browse files
committed
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 /
1 parent 6e3739a commit 5c61c09

File tree

1 file changed

+136
-131
lines changed

1 file changed

+136
-131
lines changed

modules/auxiliary/scanner/http/soap_xml.rb

Lines changed: 136 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -17,183 +17,188 @@ class Metasploit3 < Msf::Auxiliary
1717
def initialize(info = {})
1818
super(update_info(info,
1919
'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner',
20-
'Description' => %q{
20+
'Description' => %q(
2121
This module attempts to brute force SOAP/XML requests to uncover
2222
hidden methods.
23-
},
24-
'Author' => [ 'patrick' ],
23+
),
24+
'Author' => ['patrick'],
2525
'License' => MSF_LICENSE))
2626

2727
register_options(
2828
[
29-
OptString.new('PATH', [ true, "The path to test", '/']),
30-
OptString.new('XMLNAMESPACE', [ true, "XML Web Service Namespace", 'http://tempuri.org/']),
31-
OptString.new('XMLINSTANCE', [ true, "XML Schema Instance", 'http://www.w3.org/2001/XMLSchema-instance']),
32-
OptString.new('XMLSCHEMA', [ true, "XML Schema", 'http://www.w3.org/2001/XMLSchema']),
33-
OptString.new('XMLSOAP', [ true, "XML SOAP", 'http://schemas.xmlsoap.org/soap/envelope/']),
34-
OptString.new('CONTENTTYPE', [ true, "The HTTP Content-Type Header", 'application/x-www-form-urlencoded']),
35-
OptInt.new('SLEEP', [true, "Sleep this many seconds between requests", 0 ]),
36-
OptBool.new('DISPLAYHTML', [ true, "Display HTML response", false ]),
37-
OptBool.new('SSL', [ true, "Use SSL", false ]),
38-
OptBool.new('VERB_DELETE', [ false, "Enable 'delete' verb", 'false'])
29+
OptString.new('PATH', [true, 'The path to test', '/']),
30+
OptString.new('XMLNAMESPACE', [true, 'XML Web Service Namespace', 'http://tempuri.org/']),
31+
OptString.new('XMLINSTANCE', [true, 'XML Schema Instance', 'http://www.w3.org/2001/XMLSchema-instance']),
32+
OptString.new('XMLSCHEMA', [true, 'XML Schema', 'http://www.w3.org/2001/XMLSchema']),
33+
OptString.new('XMLSOAP', [true, 'XML SOAP', 'http://schemas.xmlsoap.org/soap/envelope/']),
34+
OptString.new('CONTENTTYPE', [true, 'The HTTP Content-Type Header', 'application/x-www-form-urlencoded']),
35+
OptInt.new('SLEEP', [true, 'Sleep this many milliseconds between requests', 0]),
36+
OptBool.new('DISPLAYHTML', [true, 'Display HTML response', false]),
37+
OptBool.new('SSL', [true, 'Use SSL', false]),
38+
OptBool.new('VERB_DELETE', [false, 'Enable DELETE verb', false])
3939
], self.class)
4040
end
4141

4242
# Fingerprint a single host
4343
def run_host(ip)
44+
verbs = %w(
45+
get
46+
active
47+
activate
48+
create
49+
change
50+
set
51+
put
52+
do
53+
go
54+
resolve
55+
start
56+
recover
57+
initiate
58+
negotiate
59+
define
60+
stop
61+
begin
62+
end
63+
manage
64+
administer
65+
modify
66+
register
67+
log
68+
add
69+
list
70+
query
71+
)
4472

45-
verbs = [
46-
'get',
47-
'active',
48-
'activate',
49-
'create',
50-
'change',
51-
'set',
52-
'put',
53-
'do',
54-
'go',
55-
'resolve',
56-
'start',
57-
'recover',
58-
'initiate',
59-
'negotiate',
60-
'define',
61-
'stop',
62-
'begin',
63-
'end',
64-
'manage',
65-
'administer',
66-
'modify',
67-
'register',
68-
'log',
69-
'add',
70-
'list',
71-
'query',
72-
]
73-
74-
if (datastore['VERB_DELETE'])
75-
verbs << 'delete'
76-
end
73+
verbs << 'delete' if datastore['VERB_DELETE']
74+
75+
nouns = %w(
76+
password
77+
task
78+
tasks
79+
pass
80+
administration
81+
account
82+
accounts
83+
admin
84+
login
85+
logins
86+
token
87+
tokens
88+
credential
89+
credentials
90+
key
91+
keys
92+
guid
93+
message
94+
messages
95+
user
96+
users
97+
username
98+
usernames
99+
load
100+
list
101+
name
102+
names
103+
file
104+
files
105+
path
106+
paths
107+
directory
108+
directories
109+
configuration
110+
configurations
111+
config
112+
configs
113+
setting
114+
settings
115+
registry
116+
on
117+
off
118+
)
77119

78-
nouns = [
79-
'password',
80-
'task',
81-
'tasks',
82-
'pass',
83-
'administration',
84-
'account',
85-
'accounts',
86-
'admin',
87-
'login',
88-
'logins',
89-
'token',
90-
'tokens',
91-
'credential',
92-
'credentials',
93-
'key',
94-
'keys',
95-
'guid',
96-
'message',
97-
'messages',
98-
'user',
99-
'users',
100-
'username',
101-
'usernames',
102-
'load',
103-
'list',
104-
'name',
105-
'names',
106-
'file',
107-
'files',
108-
'path',
109-
'paths',
110-
'directory',
111-
'directories',
112-
'configuration',
113-
'configurations',
114-
'config',
115-
'configs',
116-
'setting',
117-
'settings',
118-
'registry',
119-
'on',
120-
'off',
121-
]
122-
123-
target_port = datastore['RPORT']
124120
vhost = datastore['VHOST'] || wmap_target_host || ip
125121

126122
# regular expressions for common rejection messages
127123
reject_regexen = []
128-
reject_regexen << Regexp.new("method \\S+ is not valid", true)
129-
reject_regexen << Regexp.new("Method \\S+ not implemented", true)
130-
reject_regexen << Regexp.new("unable to resolve WSDL method name", true)
124+
reject_regexen << Regexp.new('method \\S+ is not valid', true)
125+
reject_regexen << Regexp.new('Method \\S+ not implemented', true)
126+
reject_regexen << Regexp.new('unable to resolve WSDL method name', true)
131127

132-
begin
133-
verbs.each do |v|
134-
nouns.each do |n|
128+
verbs.each do |v|
129+
nouns.each do |n|
130+
begin
135131
data_parts = []
136-
data_parts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
132+
data_parts << '<?xml version=\'1.0\' encoding=\'utf-8\'?>'
137133
data_parts << "<soap:Envelope xmlns:xsi=\"#{datastore['XMLINSTANCE']}\" xmlns:xsd=\"#{datastore['XMLSCHEMA']}\" xmlns:soap=\"#{datastore['XMLSOAP']}\">"
138-
data_parts << "<soap:Body>"
134+
data_parts << '<soap:Body>'
139135
data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">"
140136
data_parts << "</#{v}#{n}>"
141-
data_parts << "</soap:Body>"
142-
data_parts << "</soap:Envelope>"
137+
data_parts << '</soap:Body>'
138+
data_parts << '</soap:Envelope>'
143139
data_parts << nil
144140
data_parts << nil
145141
data = data_parts.join("\r\n")
146142

147143
uri = normalize_uri(datastore['PATH'])
148-
vprint_status("Sending request #{uri}/#{v}#{n} to #{wmap_target_host}:#{datastore['RPORT']}")
149-
150-
res = send_request_raw({
151-
'uri' => uri + '/' + v + n,
152-
'method' => 'POST',
153-
'vhost' => vhost,
154-
'data' => data,
155-
'headers' =>
156-
{
157-
'Content-Length' => data.length,
158-
'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"',
159-
'Expect' => '100-continue',
160-
'Content-Type' => datastore['CONTENTTYPE'],
161-
}
162-
}, 15)
163-
164-
if (res && !(res.body.empty?))
165-
if ((not reject_regexen.select { |r| res.body =~ r }.empty?))
144+
uri += '/' unless uri =~ /^\/$/
145+
uri += v + n
146+
147+
vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}")
148+
149+
res = send_request_raw(
150+
{
151+
'uri' => uri,
152+
'method' => 'POST',
153+
'vhost' => vhost,
154+
'data' => data,
155+
'headers' =>
156+
{
157+
'Content-Length' => data.length,
158+
'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"',
159+
'Expect' => '100-continue',
160+
'Content-Type' => datastore['CONTENTTYPE']
161+
}
162+
}, 15)
163+
164+
if res && !(res.body.empty?)
165+
if reject_regexen.any? { |r| res.body =~ r }
166166
print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.")
167-
elsif (res.message =~ /Cannot process the message because the content type/)
167+
elsif res.message =~ /Cannot process the message because the content type/
168168
print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.")
169169
res.message =~ /was not the expected type\s\'([^']+)'/
170170
print_status("Set CONTENTTYPE to \"#{$1}\"")
171171
return false
172-
elsif (res.code == 404)
172+
elsif res.code == 404
173173
print_status("Server #{wmap_target_host}:#{datastore['RPORT']} returned HTTP 404 for #{datastore['PATH']}. Use a different one.")
174174
return false
175175
else
176176
print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.")
177177
## Add Report
178178
report_note(
179-
:host => ip,
180-
:proto => 'tcp',
181-
:sname => (ssl ? 'https' : 'http'),
182-
:port => rport,
183-
:type => "SOAPAction: #{v}#{n}",
184-
:data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}."
179+
host: ip,
180+
proto: 'tcp',
181+
sname: (ssl ? 'https' : 'http'),
182+
port: rport,
183+
type: "SOAPAction: #{v}#{n}",
184+
data: "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}."
185185
)
186186
if datastore['DISPLAYHTML']
187-
print_status("The HTML content follows:")
187+
print_status('The HTML content follows:')
188188
print_status(res.body + "\r\n")
189189
end
190190
end
191191
end
192-
select(nil, nil, nil, datastore['SLEEP']) if (datastore['SLEEP'] > 0)
192+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
193+
print_error(e.message)
194+
ensure
195+
Rex.sleep(sleep_time)
193196
end
194197
end
195-
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
196-
vprint_error(e.message)
197198
end
198199
end
200+
201+
def sleep_time
202+
datastore['SLEEP'] / 1000.0
203+
end
199204
end

0 commit comments

Comments
 (0)