Land #14021, when searching modules for multiple text terms, the search will now require for all words to be matched

This commit is contained in:
adfoster-r7 2020-08-27 20:02:55 +01:00 committed by GitHub
commit 177f720f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 21 deletions

View File

@ -1,3 +1,5 @@
# -*- coding: binary -*-
require 'msf/core/modules/metadata'
#
@ -16,6 +18,11 @@ module Msf::Modules::Metadata::Search
"aux" => Msf::MODULE_AUX
}
module SearchMode
INCLUDE = 0
EXCLUDE = 1
end
#
# Parses command line search string into a hash. A param prefixed with '-' indicates "not", and will omit results
# matching that keyword. This hash can be used with the find command.
@ -52,9 +59,9 @@ module Msf::Modules::Metadata::Search
res[keyword] ||=[ [], [] ]
if search_term[0,1] == "-"
next if search_term.length == 1
res[keyword][1] << search_term[1,search_term.length-1]
res[keyword][SearchMode::EXCLUDE] << search_term[1,search_term.length-1]
else
res[keyword][0] << search_term
res[keyword][SearchMode::INCLUDE] << search_term
end
end
res
@ -87,22 +94,41 @@ module Msf::Modules::Metadata::Search
param_hash = params
[0,1].each do |mode|
[SearchMode::INCLUDE, SearchMode::EXCLUDE].each do |mode|
match = false
param_hash.keys.each do |keyword|
next if param_hash[keyword][mode].length == 0
# free form text search will honor 'and' semantics, i.e. 'metasploit pro' will only match modules that contain both
# words, and will return false when only one word is matched
if keyword == 'text'
text_segments = [module_metadata.name, module_metadata.fullname, module_metadata.description] + module_metadata.references + module_metadata.author + (module_metadata.notes['AKA'] || [])
if module_metadata.targets
text_segments = text_segments + module_metadata.targets
end
param_hash[keyword][mode].each do |search_term|
has_match = text_segments.any? { |text_segment| text_segment =~ as_regex(search_term) }
match = [keyword, search_term] if has_match
if mode == SearchMode::INCLUDE && !has_match
return false
end
if mode == SearchMode::EXCLUDE && has_match
return false
end
end
next
end
# The remaining keywords honor 'or' semantics, i.e. the following param_hash will match either osx, or linux
# {"platform"=>[["osx", "linux"], []]}
param_hash[keyword][mode].each do |search_term|
# Reset the match flag for each keyword for inclusive search
match = false if mode == 0
match = false if mode == SearchMode::INCLUDE
# Convert into a case-insensitive regex
utf8_buf = search_term.dup.force_encoding('UTF-8')
if utf8_buf.valid_encoding?
regex = Regexp.new(Regexp.escape(utf8_buf), true)
else
return false
end
regex = as_regex(search_term)
case keyword
when 'aka'
match = [keyword, search_term] if (module_metadata.notes['AKA'] || []).any? { |aka| aka =~ regex }
@ -176,13 +202,6 @@ module Msf::Modules::Metadata::Search
match = [keyword, search_term] if module_metadata.references.any? { |ref| ref =~ regex }
when 'target', 'targets'
match = [keyword, search_term] if module_metadata.targets.any? { |target| target =~ regex }
when 'text'
terms = [module_metadata.name, module_metadata.fullname, module_metadata.description] + module_metadata.references + module_metadata.author + (module_metadata.notes['AKA'] || [])
if module_metadata.targets
terms = terms + module_metadata.targets
end
match = [keyword, search_term] if terms.any? { |term| term =~ regex }
when 'type'
match = [keyword, search_term] if Msf::MODULE_TYPES.any? { |module_type| search_term == module_type and module_metadata.type == module_type }
else
@ -192,12 +211,12 @@ module Msf::Modules::Metadata::Search
break if match
end
# Filter this module if no matches for a given keyword type
if mode == 0 and not match
if mode == SearchMode::INCLUDE and not match
return false
end
end
# Filter this module if we matched an exclusion keyword (-value)
if mode == 1 and match
if mode == SearchMode::EXCLUDE and match
return false
end
end
@ -205,6 +224,17 @@ module Msf::Modules::Metadata::Search
true
end
def as_regex(search_term)
# Convert into a case-insensitive regex
utf8_buf = search_term.dup.force_encoding('UTF-8')
if utf8_buf.valid_encoding?
Regexp.new(Regexp.escape(utf8_buf), Regexp::IGNORECASE)
else
# If the encoding is invalid, default to a regex that matches anything
//
end
end
def get_fields(module_metadata, fields)
selected_fields = {}

View File

@ -669,7 +669,7 @@ module Msf
# Avoid trying to use the search result if it exactly matches
# the module we were trying to load. The module cannot be
# loaded and searching isn't going to change that.
mods_found = cmd_search('-I', '-u', mod_name)
mods_found = cmd_search('-I', '-u', *args)
end
unless mods_found

View File

@ -1,3 +1,5 @@
# -*- coding: binary -*-
require 'spec_helper'
RSpec.describe Msf::Modules::Metadata::Search do
@ -203,6 +205,49 @@ RSpec.describe Msf::Modules::Metadata::Search do
it_should_behave_like 'search_filter', :accept => [nil, '', ' '], :test_inverse => false
end
context 'on a module with a #description of "metasploit pro console"' do
let(:opts) { ({ 'description' => 'metasploit pro console' }) }
it_should_behave_like(
'search_filter',
:accept => ["metasploit", "metasploit pro", "metasploit pro console", "console pro"],
:reject => ["metasploit framework", "pro framework", "pro console php"],
:test_inverse => false
)
end
context 'when invalid encodings are used, all results are returned' do
context 'and the search term is present' do
let(:opts) { ({ 'author' => ['István'.force_encoding("UTF-8")] }) }
it_should_behave_like(
'search_filter',
accept: [
"author:István",
"author:Istv\xE1n ",
"author:Istv\u00E1n ",
],
:reject => [
'different_author'
],
:test_inverse => false
)
end
context 'and the search term is not present' do
let(:opts) { ({ 'author' => ['different_author'] }) }
it_should_behave_like(
'search_filter',
accept: [
'different_author',
"author:Istv\xE1n",
],
:reject => [
"author:István",
"author:Istv\u00E1n ",
],
:test_inverse => false
)
end
end
context 'when filtering by module #type' do
all_module_types = Msf::MODULE_TYPES
all_module_types.each do |mtype|