Class: Rex::Post::SMB::Ui::Console::CommandDispatcher::Shares

Inherits:
Object
  • Object
show all
Includes:
Rex::Post::SMB::Ui::Console::CommandDispatcher
Defined in:
lib/rex/post/smb/ui/console/command_dispatcher/shares.rb

Overview

Core SMB client commands

Constant Summary collapse

@@shares_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ],
  ['-l', '--list'] => [ false, 'List all shares'],
  ['-i', '--interact'] => [ true, 'Interact with the supplied share ID', '<id>']
)
@@ls_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@pwd_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@cd_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@cat_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@upload_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@download_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@delete_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@mkdir_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)
@@rmdir_opts =
Rex::Parser::Arguments.new(
  ['-h', '--help'] => [false, 'Help menu' ]
)

Instance Attribute Summary

Attributes included from Ui::Text::DispatcherShell::CommandDispatcher

#shell, #tab_complete_items

Instance Method Summary collapse

Methods included from Rex::Post::SMB::Ui::Console::CommandDispatcher

#active_share, #client, #docs_dir, #filter_commands, #log_error, #msf_loaded?, #session, #simple_client, #unknown_command

Methods included from Msf::Ui::Console::CommandDispatcher::Session

#cmd_background, #cmd_background_help, #cmd_exit, #cmd_irb, #cmd_irb_help, #cmd_irb_tabs, #cmd_pry, #cmd_pry_help, #cmd_resource, #cmd_resource_help, #cmd_resource_tabs, #cmd_sessions, #cmd_sessions_help

Methods included from Ui::Text::DispatcherShell::CommandDispatcher

#cmd_help, #cmd_help_help, #cmd_help_tabs, #deprecated_cmd, #deprecated_commands, #deprecated_help, #docs_dir, #help_to_s, included, #print, #print_error, #print_good, #print_line, #print_status, #print_warning, #tab_complete_directory, #tab_complete_filenames, #tab_complete_generic, #tab_complete_source_address, #unknown_command, #update_prompt

Constructor Details

#initialize(console) ⇒ Shares

Initializes an instance of the shares command set using the supplied console for interactivity.

Parameters:



25
26
27
28
29
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 25

def initialize(console)
  super

  @share_search_results = []
end

Instance Method Details

#cmd_cat(*args) ⇒ Object

Print the contents of a file



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 335

def cmd_cat(*args)
  if args.include?('-h') || args.include?('--help') || args.length != 1
    cmd_cd_help
    return
  end

  return print_no_share_selected unless active_share

  path = args[0]

  new_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(path).to_s)

  begin
    file = simple_client.open(new_path, 'o')
    result = file.read
    print_line(result)
  rescue StandardError => e
    print_error("#{e.class} #{e}")
    return
  ensure
    begin
      file.close if file
    rescue StandardError => e
      elog(e)
    end
  end
end

#cmd_cat_helpObject



325
326
327
328
329
330
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 325

def cmd_cat_help
  print_line 'Usage: cat <path>'
  print_line
  print_line 'Read the file at the given path.'
  print_line
end

#cmd_cd(*args) ⇒ Object

Change directory



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 291

def cmd_cd(*args)
  if args.include?('-h') || args.include?('--help') || args.length != 1
    cmd_cd_help
    return
  end

  return print_no_share_selected unless active_share

  path = args[0]
  native_path = Pathname.new(shell.cwd).join(path).to_s
  new_path = Rex::Ntpath.as_ntpath(native_path)
  begin
    response = active_share.open_directory(directory: new_path)
    directory = RubySMB::SMB2::File.new(name: new_path, tree: active_share, response: response, encrypt: @tree_connect_encrypt_data)
  rescue RubySMB::Error::UnexpectedStatusCode => e
    # Special case this error to provide better feedback to the user
    # since I think trying to `cd` to a non-existent directory is pretty likely to accidentally happen
    if e.status_code == WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
      print_error("The path `#{new_path}` is not a valid directory")
    end
    print_error(e.message)
    elog(e)
    return
  rescue StandardError => e
    print_error('Unknown error occurred while trying to change directory')
    elog(e)
    return
  ensure
    directory.close if directory
  end

  shell.cwd = native_path
end

#cmd_cd_helpObject



281
282
283
284
285
286
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 281

def cmd_cd_help
  print_line 'Usage: cd <path>'
  print_line
  print_line 'Change the current remote working directory.'
  print_line
end

#cmd_cd_tabs(_str, words) ⇒ Object



363
364
365
366
367
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 363

def cmd_cd_tabs(_str, words)
  return [] if words.length > 1

  @@cd_opts.option_keys
end

#cmd_delete(*args) ⇒ Object



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 461

def cmd_delete(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_delete_help
    return
  end
  remote_path = nil

  @@delete_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      remote_path = val
    else
      print_warning('Too many parameters')
      cmd_delete_help
      return
    end
  end

  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)
  fd = simple_client.open(full_path, 'o')
  fd.delete
  print_good("Deleted #{full_path}")
end

#cmd_delete_helpObject



485
486
487
488
489
490
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 485

def cmd_delete_help
  print_line 'Usage: delete <remote_path>'
  print_line
  print_line 'Delete a file from the remote target.'
  print @@delete_opts.usage
end

#cmd_download(*args) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 417

def cmd_download(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_download_help
    return
  end

  return print_no_share_selected unless active_share

  remote_path = nil
  local_path = nil

  @@download_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      remote_path = val
    when 1
      local_path = val
    else
      print_warning('Too many parameters')
      cmd_download_help
      return
    end
  end

  if remote_path.blank?
    print_error('No remote path given')
    return
  end

  local_path = Rex::Post::File.basename(remote_path) if local_path.nil?
  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)

  download_file(local_path, full_path)

  print_good("Downloaded #{full_path} to #{local_path}")
end

#cmd_download_helpObject



454
455
456
457
458
459
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 454

def cmd_download_help
  print_line 'Usage: download <remote_path> <local_path>'
  print_line
  print_line 'Download a file from the remote target.'
  print @@download_opts.usage
end

#cmd_ls(*args) ⇒ Object Also known as: cmd_dir

Display the contents of your current working directory



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
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 179

def cmd_ls(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_ls_help
    return
  end

  return print_no_share_selected unless active_share

  remote_path = ''

  @@delete_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      remote_path = val
    else
      print_warning('Too many parameters')
      cmd_ls_help
      return
    end
  end

  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)

  files = active_share.list(directory: full_path)
  table = Rex::Text::Table.new(
    'Header' => "ls #{full_path}",
    'Indent' => 4,
    'Columns' => [ '#', 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size'],
    'Rows' => files.map.with_index do |file, i|
      name = file.file_name.encode('UTF-8')
      create_time = file.create_time.to_datetime
      last_access = file.last_access.to_datetime
      last_write = file.last_write.to_datetime
      last_change = file.last_change.to_datetime
      if (file[:file_attributes]&.directory == 1) || (file[:ext_file_attributes]&.directory == 1)
        type = 'DIR'
      else
        type = 'FILE'
        size = file.end_of_file
      end

      [i, type || 'Unknown', name, create_time, last_access, last_write, last_change, size]
    end
  )

  print_line table.to_s
end

#cmd_ls_helpObject Also known as: cmd_dir_help



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 227

def cmd_ls_help
  print_line 'Usage:'
  print_line 'ls [options] [path]'
  print_line
  print_line 'COMMAND ALIASES:'
  print_line
  print_line '    dir'
  print_line
  print_line 'Lists contents of directory or file info'
  print_line @@ls_opts.usage
end

#cmd_ls_tabs(_str, words) ⇒ Object Also known as: cmd_dir_tabs



239
240
241
242
243
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 239

def cmd_ls_tabs(_str, words)
  return [] if words.length > 1

  @@ls_opts.option_keys
end

#cmd_mkdir(*args) ⇒ Object



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 492

def cmd_mkdir(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_mkdir_help
    return
  end

  return print_no_share_selected unless active_share

  remote_path = nil

  @@mkdir_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      remote_path = val
    else
      print_warning('Too many parameters')
      cmd_mkdir_help
      return
    end
  end

  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)

  response = active_share.open_directory(directory: full_path, disposition: RubySMB::Dispositions::FILE_CREATE)
  directory = RubySMB::SMB2::File.new(name: full_path, tree: active_share, response: response, encrypt: @tree_connect_encrypt_data)
  print_good("Directory #{full_path} created")
ensure
  directory.close if directory
end

#cmd_mkdir_helpObject



522
523
524
525
526
527
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 522

def cmd_mkdir_help
  print_line 'Usage: mkdir <remote_path>'
  print_line
  print_line 'Create a directory on the remote target.'
  print @@mkdir_opts.usage
end

#cmd_pwd(*args) ⇒ Object

Print the current working directory



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 262

def cmd_pwd(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_pwd_help
    return
  end

  return print_no_share_selected unless active_share

  share_name = active_share.share[/[^\\].*$/, 0]
  cwd = shell.cwd.blank? ? '' : "\\#{shell.cwd}"
  print_line "Current directory is \\\\#{share_name}#{cwd}\\"
end

#cmd_pwd_helpObject



252
253
254
255
256
257
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 252

def cmd_pwd_help
  print_line 'Usage: pwd'
  print_line
  print_line 'Print the current remote working directory.'
  print_line
end

#cmd_pwd_tabs(_str, words) ⇒ Object



275
276
277
278
279
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 275

def cmd_pwd_tabs(_str, words)
  return [] if words.length > 1

  @@pwd_opts.option_keys
end

#cmd_rmdir(*args) ⇒ Object



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 529

def cmd_rmdir(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_rmdir_help
    return
  end

  return print_no_share_selected unless active_share

  remote_path = nil

  @@rmdir_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      remote_path = val
    else
      print_warning('Too many parameters')
      cmd_rmdir_help
      return
    end
  end

  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)

  response = active_share.open_directory(directory: full_path, write: true, delete: true, desired_delete: true)
  directory = RubySMB::SMB2::File.new(name: full_path, tree: active_share, response: response, encrypt: @tree_connect_encrypt_data)
  status = directory.delete
  if status == WindowsError::NTStatus::STATUS_SUCCESS
    print_good("Deleted #{full_path}")
  else
    print_error("Error deleting #{full_path}: #{status.name}, #{status.description}")
  end
ensure
  directory.close if directory
end

#cmd_rmdir_helpObject



564
565
566
567
568
569
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 564

def cmd_rmdir_help
  print_line 'Usage: rmdir <remote_path>'
  print_line
  print_line 'Delete a directory from the remote target.'
  print @@rmdir_opts.usage
end

#cmd_shares(*args) ⇒ Object

View and interact with shares



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
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 106

def cmd_shares(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_shares_help
    return
  end

  method = :list
  share_name = nil

  # Parse options
  @@shares_opts.parse(args) do |opt, _idx, val|
    case opt
    when '-l', '--list'
    when '-i', '--interact'
      share_name = val
      method = :interact
    end
  end

  # Perform action
  case method
  when :list
    populate_shares

    table = Rex::Text::Table.new(
      'Header' => 'Shares',
      'Indent' => 4,
      'Columns' => %w[# Name Type comment],
      'Rows' => @share_search_results.map.with_index do |share, i|
        [i, share[:name], share[:type], share[:comment]]
      end
    )

    print_line table.to_s
  when :interact
    populate_shares if @valid_share_names.nil?
    # Share names can be comprised only of digits so prioritise a share name over the share index
    if share_name.match?(/\A\d+\z/) && !@valid_share_names.include?(share_name)
      share_name = (@share_search_results[share_name.to_i] || {})[:name]
    end

    if share_name.nil?
      print_error('Invalid share name')
      return
    end

    path = "\\\\#{session.address}\\#{share_name}"
    begin
      shell.active_share = client.tree_connect(path)
      shell.cwd = ''
      print_good "Successfully connected to #{share_name}"
    rescue StandardError => e
      log_error("Error running action #{method}: #{e.class} #{e}")
    end
  end
end

#cmd_shares_helpObject



169
170
171
172
173
174
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 169

def cmd_shares_help
  print_line 'Usage: shares'
  print_line
  print_line 'View the shares available on the remote target.'
  print @@shares_opts.usage
end

#cmd_shares_tabs(_str, words) ⇒ Object



163
164
165
166
167
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 163

def cmd_shares_tabs(_str, words)
  return [] if words.length > 1

  @@shares_opts.option_keys
end

#cmd_upload(*args) ⇒ Object



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 369

def cmd_upload(*args)
  if args.include?('-h') || args.include?('--help')
    cmd_upload_help
    return
  end

  return print_no_share_selected unless active_share

  local_path = nil
  remote_path = nil

  @@upload_opts.parse(args) do |_opt, idx, val|
    case idx
    when 0
      local_path = val
    when 1
      remote_path = val
    else
      print_warning('Too many parameters')
      cmd_upload_help
      return
    end
  end

  if local_path.blank?
    print_error('No local path given')
    return
  end

  remote_path = Rex::Post::File.basename(local_path) if remote_path.nil?
  full_path = Rex::Ntpath.as_ntpath(Pathname.new(shell.cwd).join(remote_path).to_s)

  upload_file(full_path, local_path)

  print_good("#{local_path} uploaded to #{full_path}")
end

#cmd_upload_helpObject



410
411
412
413
414
415
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 410

def cmd_upload_help
  print_line 'Usage: upload <local_path> <remote_path>'
  print_line
  print_line 'Upload a file to the remote target.'
  print @@upload_opts.usage
end

#cmd_upload_tabs(str, words) ⇒ Object



406
407
408
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 406

def cmd_upload_tabs(str, words)
  tab_complete_filenames(str, words)
end

#commandsObject

List of supported commands.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 76

def commands
  cmds = {
    'shares' => 'View the available shares and interact with one',
    'ls' => 'List all files in the current directory',
    'dir' => 'List all files in the current directory (alias for ls)',
    'pwd' => 'Print the current remote working directory',
    'cd' => 'Change the current remote working directory',
    'cat' => 'Read the file at the given path',
    'upload' => 'Upload a file',
    'download' => 'Download a file',
    'delete' => 'Delete a file',
    'mkdir' => 'Make a new directory',
    'rmdir' => 'Delete a directory'
  }

  reqs = {}

  filter_commands(cmds, reqs)
end

#download_file(dest_file, src_file) ⇒ Object (protected)

Download a remote file from the target

Parameters:

  • dest_file (String)

    The path for the destination file

  • src_file (String)

    The path for the source file



604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 604

def download_file(dest_file, src_file)
  buf_size = 8 * 1024 * 1024
  src_fd = simple_client.open(src_file, 'o')
  # Make the destination path if necessary
  dir = ::File.dirname(dest_file)
  ::FileUtils.mkdir_p(dir) if dir && !::File.directory?(dir)
  dst_fd = ::File.new(dest_file, "wb")

  offset = 0
  src_size = client.open_files[src_fd.file_id].size
  begin
    while offset < src_size
      data = src_fd.read(buf_size, offset)
      dst_fd.write(data)
      offset += data.length
      percent = offset / src_size.to_f * 100.0
      msg = "Downloaded #{Filesize.new(offset).pretty} of " \
        "#{Filesize.new(src_size).pretty} (#{percent.round(2)}%)"
      print_status(msg)
    end
  ensure
    src_fd.close unless src_fd.nil?
    dst_fd.close unless dst_fd.nil?
  end
end

#nameObject

Shares



99
100
101
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 99

def name
  'Shares'
end


573
574
575
576
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 573

def print_no_share_selected
  print_error('No active share selected. Use the %grnshares%clr command to view available shares, and %grnshares -i <id>%clr to interact with one')
  nil
end

#upload_file(dest_file, src_file) ⇒ Object (protected)

Upload a local file to the target

Parameters:

  • dest_file (String)

    The path for the destination file

  • src_file (String)

    The path for the source file



581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/rex/post/smb/ui/console/command_dispatcher/shares.rb', line 581

def upload_file(dest_file, src_file)
  buf_size = 8 * 1024 * 1024
  begin
    dest_fd = simple_client.open(dest_file, 'wct', write: true)
    src_fd = ::File.open(src_file, "rb")
    src_size = src_fd.stat.size
    offset = 0
    while (buf = src_fd.read(buf_size))
      offset = dest_fd.write(buf, offset)
      percent = offset / src_size.to_f * 100.0
      msg = "Uploaded #{Filesize.new(offset).pretty} of " \
      "#{Filesize.new(src_size).pretty} (#{percent.round(2)}%)"
      print_status(msg)
    end
  ensure
    src_fd.close unless src_fd.nil?
    dest_fd.close unless dest_fd.nil?
  end
end