Land #15441, add date filtering to stdapi_fs_search

This commit is contained in:
Tim W 2021-09-28 15:55:43 +01:00
commit 4289c8b3ea
25 changed files with 263 additions and 38 deletions

View File

@ -29,9 +29,9 @@ PATH
metasploit-concern
metasploit-credential
metasploit-model
metasploit-payloads (= 2.0.56)
metasploit-payloads (= 2.0.57)
metasploit_data_models
metasploit_payloads-mettle (= 1.0.12)
metasploit_payloads-mettle (= 1.0.13)
mqtt
msgpack
nessus_rest
@ -256,7 +256,7 @@ GEM
activemodel (~> 6.0)
activesupport (~> 6.0)
railties (~> 6.0)
metasploit-payloads (2.0.56)
metasploit-payloads (2.0.57)
metasploit_data_models (5.0.4)
activerecord (~> 6.0)
activesupport (~> 6.0)
@ -267,7 +267,7 @@ GEM
railties (~> 6.0)
recog (~> 2.0)
webrick
metasploit_payloads-mettle (1.0.12)
metasploit_payloads-mettle (1.0.13)
method_source (1.0.0)
mini_portile2 (2.6.1)
minitest (5.14.4)

View File

@ -77,9 +77,9 @@ metasploit-concern, 4.0.3, "New BSD"
metasploit-credential, 5.0.4, "New BSD"
metasploit-framework, 6.1.8, "New BSD"
metasploit-model, 4.0.3, "New BSD"
metasploit-payloads, 2.0.56, "3-clause (or ""modified"") BSD"
metasploit-payloads, 2.0.57, "3-clause (or ""modified"") BSD"
metasploit_data_models, 5.0.4, "New BSD"
metasploit_payloads-mettle, 1.0.12, "3-clause (or ""modified"") BSD"
metasploit_payloads-mettle, 1.0.13, "3-clause (or ""modified"") BSD"
method_source, 1.0.0, MIT
mini_portile2, 2.6.1, MIT
minitest, 5.14.4, MIT

View File

@ -75,7 +75,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
#
# Raises a RequestError if +root+ is not a directory.
#
def File.search( root=nil, glob="*.*", recurse=true, timeout=-1 )
def File.search( root=nil, glob="*.*", recurse=true, timeout=-1, modified_start_date=nil, modified_end_date=nil)
files = ::Array.new
@ -87,6 +87,8 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
request.add_tlv( TLV_TYPE_SEARCH_ROOT, root )
request.add_tlv( TLV_TYPE_SEARCH_GLOB, glob )
request.add_tlv( TLV_TYPE_SEARCH_RECURSE, recurse )
request.add_tlv( TLV_TYPE_SEARCH_M_START_DATE, modified_start_date) if modified_start_date
request.add_tlv( TLV_TYPE_SEARCH_M_END_DATE, modified_end_date) if modified_end_date
# we set the response timeout to -1 to wait indefinitely as a
# search could take an indeterminate amount of time to complete.
@ -96,7 +98,8 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
files << {
'path' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_PATH).chomp( self.separator )),
'name' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_NAME)),
'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE)
'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE),
'mtime'=> results.get_tlv_value(TLV_TYPE_SEARCH_MTIME)
}
end
end

View File

@ -47,6 +47,10 @@ TLV_TYPE_SEARCH_RECURSE = TLV_META_TYPE_BOOL | 1230
TLV_TYPE_SEARCH_GLOB = TLV_META_TYPE_STRING | 1231
TLV_TYPE_SEARCH_ROOT = TLV_META_TYPE_STRING | 1232
TLV_TYPE_SEARCH_RESULTS = TLV_META_TYPE_GROUP | 1233
TLV_TYPE_SEARCH_MTIME = TLV_META_TYPE_UINT | 1235
TLV_TYPE_SEARCH_M_START_DATE= TLV_META_TYPE_UINT | 1236
TLV_TYPE_SEARCH_M_END_DATE = TLV_META_TYPE_UINT | 1237
TLV_TYPE_FILE_MODE_T = TLV_META_TYPE_UINT | 1234
##

View File

@ -134,6 +134,14 @@ class Console::CommandDispatcher::Stdapi::Fs
"Stdapi: File system"
end
def vali_date(str)
result = DateTime.parse(str)
return result.to_time.to_i
rescue
print_error("Bad date/time specification (#{str}). Use this format: \"YYYY-mm-dd\" or \"YYYY-mm-ddTHH:MM:SS\", e.g \"1970-01-01\"")
nil
end
#
# Search for files.
#
@ -143,12 +151,16 @@ class Console::CommandDispatcher::Stdapi::Fs
recurse = true
globs = []
files = []
modified_start_date = nil
modified_end_date = nil
opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help Banner" ],
"-d" => [ true, "The directory/drive to begin searching from. Leave empty to search all drives. (Default: #{root})" ],
"-f" => [ true, "A file pattern glob to search for. (e.g. *secret*.doc?)" ],
"-r" => [ true, "Recursively search sub directories. (Default: #{recurse})" ]
"-r" => [ true, "Recursively search sub directories. (Default: #{recurse})" ],
"-a" => [ true, "Find files modified after timestamp (UTC). Format: YYYY-mm-dd or YYYY-mm-ddTHH:MM:SS"],
"-b" => [ true, "Find files modified before timestamp (UTC). Format: YYYY-mm-dd or YYYY-mm-ddTHH:MM:SS"]
)
opts.parse(args) { | opt, idx, val |
@ -164,6 +176,12 @@ class Console::CommandDispatcher::Stdapi::Fs
globs << val
when "-r"
recurse = false if val =~ /^(f|n|0)/i
when "-a"
modified_start_date = vali_date(val)
return unless modified_start_date
when "-b"
modified_end_date = vali_date(val)
return unless modified_end_date
end
}
@ -173,7 +191,7 @@ class Console::CommandDispatcher::Stdapi::Fs
end
globs.uniq.each do |glob|
files += client.fs.file.search(root, glob, recurse)
files += client.fs.file.search(root, glob, recurse, -1, modified_start_date, modified_end_date)
end
if files.empty?
@ -181,15 +199,27 @@ class Console::CommandDispatcher::Stdapi::Fs
return
end
print_line("Found #{files.length} result#{ files.length > 1 ? 's' : '' }...")
files.each do | file |
if file['size'] > 0
print(" #{file['path']}#{ file['path'].empty? ? '' : client.fs.file.separator }#{file['name']} (#{file['size']} bytes)\n")
else
print(" #{file['path']}#{ file['path'].empty? ? '' : client.fs.file.separator }#{file['name']}\n")
end
end
header = "Found #{files.length} result#{ files.length > 1 ? 's' : '' }..."
results_table = Rex::Text::Table.new(
'WordWrap' => false,
'Width' => 120,
'Header' => header,
'Indent' => 0,
'SortIndex' => 0,
'Columns' => ['Path', 'Size (bytes)', 'Modified (UTC)'],
)
files.each do | file |
filestr = ''
unless file['path'].empty?
filestr += "#{file['path']}#{client.fs.file.separator}"
end
filestr += file['name']
datestr = ''
datestr = Time.at(file['mtime']).to_s if file['mtime']
results_table << [filestr, file['size'], datestr]
end
print_line results_table.to_s
end
#

View File

@ -70,9 +70,9 @@ Gem::Specification.new do |spec|
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model'
# Needed for Meterpreter
spec.add_runtime_dependency 'metasploit-payloads', '2.0.56'
spec.add_runtime_dependency 'metasploit-payloads', '2.0.57'
# Needed for the next-generation POSIX Meterpreter
spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.12'
spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.13'
# Needed by msfgui and other rpc components
spec.add_runtime_dependency 'msgpack'
# get list of network interfaces, like eth* from OS.

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026792
CachedSize = 1027040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026792
CachedSize = 1027040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026792
CachedSize = 1027040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026920
CachedSize = 1027168
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026920
CachedSize = 1027168
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1026920
CachedSize = 1027168
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1573904
CachedSize = 1573952
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1573904
CachedSize = 1573952
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1573904
CachedSize = 1573952
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1468324
CachedSize = 1468596
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1468324
CachedSize = 1468596
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1468324
CachedSize = 1468596
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1471264
CachedSize = 1471536
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1471264
CachedSize = 1471536
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 1471264
CachedSize = 1471536
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 810048
CachedSize = 810040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 810048
CachedSize = 810040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -6,7 +6,7 @@
module MetasploitModule
CachedSize = 810048
CachedSize = 810040
include Msf::Payload::Single
include Msf::Sessions::MeterpreterOptions

View File

@ -0,0 +1,188 @@
require 'rex/post/meterpreter/extensions/stdapi/command_ids'
require 'rex'
lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
def initialize(info={})
super( update_info( info,
'Name' => 'Testing Meterpreter Search',
'Description' => %q{ This module will test the meterpreter search method },
'License' => MSF_LICENSE,
'Author' => [ 'timwr'],
'Platform' => [ 'windows', 'linux', 'java' ],
'SessionTypes' => [ 'meterpreter' ]
))
register_options(
[
OptBool.new("AddEntropy" , [false, "Add entropy token to file and directory names.", false]),
OptString.new("BaseFileName" , [true, "File/dir base name", "meterpreter-test"])
], self.class)
end
def setup
@old_pwd = session.fs.dir.getwd
stat = session.fs.file.stat("/tmp") rescue nil
if (stat and stat.directory?)
tmp = "/tmp"
else
tmp = session.sys.config.getenv('TEMP')
end
vprint_status("Setup: changing working directory to #{tmp}")
session.fs.dir.chdir(tmp)
if datastore["AddEntropy"]
entropy_value = '-' + ('a'..'z').to_a.shuffle[0,8].join
else
entropy_value = ""
end
@file_name = "#{datastore["BaseFileName"]}#{entropy_value}.txt"
vprint_status("Source File Name: #{@file_name}")
session.fs.file.rm(@file_name) rescue nil
fd = session.fs.file.open(@file_name, "wb")
fd.close
super
end
def test_fs_search
vprint_status("Starting search tests")
pwd = session.fs.dir.getwd
it "should search for new files" do
res = true
found = false
files = client.fs.file.search(pwd, "*", false)
files.each do |file|
if file['name'] == @file_name
res &&= (found == false)
found = true
res &&= (file['path'] == pwd)
end
end
res &&= found
res
end
it "should search recursively for files" do
res = true
found = false
files = client.fs.file.search(pwd, "*", true)
files.each do |file|
if file['name'] == @file_name
res &&= (found == false)
found = true
res &&= (file['path'] == pwd)
end
end
res &&= found
res
end
it "should search with globs for files" do
res = true
found = false
files = client.fs.file.search(pwd, "*.txt", true)
files.each do |file|
if file['name'] == @file_name
res &&= (found == false)
found = true
res &&= (file['path'] == pwd)
end
end
res &&= found
res
end
it "should search with globs ignoring files" do
res = true
files = client.fs.file.search(pwd, "*.ignoretxt", true)
files.each do |file|
res = false
end
res
end
end
def test_fs_search_date
vprint_status("Starting search date tests")
pwd = session.fs.dir.getwd
yesterday = (Time.now - 1.week).to_i
tomorrow = (Time.now + 1.week).to_i
it "should search with dates for files" do
res = true
found = false
files = client.fs.file.search(pwd, "*", true, -1, yesterday, tomorrow)
files.each do |file|
if file['name'] == @file_name
res &&= (found == false)
res &&= (file['path'] == pwd)
res &&= (file['mtime'] > yesterday)
res &&= (file['mtime'] < tomorrow)
found = true
end
end
res &&= found
res
end
it "should search with dates ignores new files" do
res = true
files = client.fs.file.search(pwd, "*", true, -1, tomorrow, nil)
files.each do |file|
res = false if file['name'] == @file_name
end
res
end
it "should search with dates ignores old files" do
res = true
files = client.fs.file.search(pwd, "*", true, -1, nil, yesterday)
files.each do |file|
res = false if file['name'] == @file_name
end
res
end
genesis_date = "3 January 2009 18:15:13 +0000"
genesis = DateTime.parse(genesis_date).to_i
if not ['windows', 'win'].include? session.platform
cmd_exec("touch -d '#{genesis_date}' #{@file_name}")
elsif session.priv.present?
client.priv.fs.set_file_mace(@file_name, genesis)
else
vprint_status("Session does not support setting the modified date, skipping exact date tests")
return
end
it "should search with date inclusive of exact date" do
res = false
files = client.fs.file.search(pwd, "*", true, -1, genesis, genesis)
files.each do |file|
if file['name'] == @file_name
res = (file['mtime'] == genesis)
end
end
res
end
end
def cleanup
session.fs.file.rm(@file_name) rescue nil
vprint_status("Cleanup: changing working directory back to #{@old_pwd}")
session.fs.dir.chdir(@old_pwd)
super
end
end