Class: Recog::Fingerprint
- Inherits:
-
Object
- Object
- Recog::Fingerprint
- Defined in:
- lib/recog/fingerprint.rb,
lib/recog/fingerprint/regexp_factory.rb
Overview
A fingerprint that can be matched against a particular kind of
fingerprintable data, e.g. an HTTP Server
header
Defined Under Namespace
Modules: RegexpFactory Classes: Test
Instance Attribute Summary collapse
-
#name ⇒ String
readonly
A human readable name describing this fingerprint.
-
#params ⇒ Hash<String,Array>
readonly
Collection of indexes for capture groups created by #match.
-
#regex ⇒ Regexp
readonly
Regular expression pulled from the DB xml file.
- #tests ⇒ void readonly
Instance Method Summary collapse
-
#initialize(xml, match_key = nil, protocol = nil) ⇒ Fingerprint
constructor
A new instance of Fingerprint.
-
#match(match_string) ⇒ Hash?
Attempt to match the given string.
- #output_diag_data(message, data, exception) ⇒ Object
-
#verify_params {|status, message| ... } ⇒ Object
Ensure all the #params are valid.
-
#verify_tests {|status, message| ... } ⇒ Object
Ensure all the #tests actually match the fingerprint and return the expected capture groups.
-
#verify_tests_have_capture_groups {|status, message| ... } ⇒ Object
For fingerprints that specify parameters that are defined by capture groups, ensure that each parameter has at least one test that defines an attribute to test for the correct capture of that parameter.
Constructor Details
#initialize(xml, match_key = nil, protocol = nil) ⇒ Fingerprint
Returns a new instance of Fingerprint.
34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/recog/fingerprint.rb', line 34 def initialize(xml, match_key=nil, protocol=nil) @match_key = match_key @protocol = protocol @name = parse_description(xml) @regex = create_regexp(xml) @params = {} @tests = [] @protocol.downcase! if @protocol parse_examples(xml) parse_params(xml) end |
Instance Attribute Details
#name ⇒ String (readonly)
A human readable name describing this fingerprint
13 14 15 |
# File 'lib/recog/fingerprint.rb', line 13 def name @name end |
#params ⇒ Hash<String,Array> (readonly)
Collection of indexes for capture groups created by #match
24 25 26 |
# File 'lib/recog/fingerprint.rb', line 24 def params @params end |
#regex ⇒ Regexp (readonly)
Regular expression pulled from the DB xml file.
19 20 21 |
# File 'lib/recog/fingerprint.rb', line 19 def regex @regex end |
Instance Method Details
#match(match_string) ⇒ Hash?
Attempt to match the given string.
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 |
# File 'lib/recog/fingerprint.rb', line 60 def match(match_string) # match_string.force_encoding('BINARY') if match_string begin match_data = @regex.match(match_string) rescue Encoding::CompatibilityError => e begin # Replace invalid UTF-8 characters with spaces, just as DAP does. encoded_str = match_string.encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => '') match_data = @regex.match(encoded_str) rescue Exception => e output_diag_data('Exception while re-encoding match_string to UTF-8', match_string, e) end rescue Exception => e output_diag_data('Exception while running regex against match_string', match_string, e) end return if match_data.nil? result = { 'matched' => @name } replacements = {} @params.each_pair do |k,v| pos = v[0] if pos == 0 # A match offset of 0 means this param has a hardcoded value result[k] = v[1] # if this value uses interpolation, note it for handling later v[1].scan(/\{([^\s{}]+)\}/).flatten.each do |replacement| replacements[k] ||= Set[] replacements[k] << replacement end else # A match offset other than 0 means the value should come from # the corresponding match result index result[k] = match_data[ pos ] end end # Use the protocol specified in the XML database if there isn't one # provided as part of this fingerprint. if @protocol unless result['service.protocol'] result['service.protocol'] = @protocol end end result['fingerprint_db'] = @match_key if @match_key # for everything identified as using interpolation, do so replacements.each_pair do |replacement_k, replacement_vs| replacement_vs.each do |replacement| if result[replacement] result[replacement_k] = result[replacement_k].gsub(/\{#{replacement}\}/, result[replacement]) else # if the value uses an interpolated value that does not exist, in general this could be # very bad, but over time we have allowed the use of regexes with # optional captures that are then used for parts of the asserted # fingerprints. This is frequently done for optional version # strings. If the key in question is cpe23 and the interpolated # value we are trying to replace is version related, use the CPE # standard of '-' for the version, otherwise raise and exception as # this code currently does not handle interpolation of undefined # values in other cases. if replacement_k =~ /\.cpe23$/ and replacement =~ /\.version$/ result[replacement_k] = result[replacement_k].gsub(/\{#{replacement}\}/, '-') else raise "Invalid use of nil interpolated non-version value #{replacement} in non-cpe23 fingerprint param #{replacement_k}" end end end end return result end |
#output_diag_data(message, data, exception) ⇒ Object
47 48 49 50 51 52 53 54 |
# File 'lib/recog/fingerprint.rb', line 47 def output_diag_data(, data, exception) STDERR.puts STDERR.puts exception.inspect STDERR.puts "Length: #{data.length}" STDERR.puts "Encoding: #{data.encoding}" STDERR.puts "Problematic data:\n#{data}" STDERR.puts "Raw bytes:\n#{data.pretty_inspect}\n" end |
#verify_params {|status, message| ... } ⇒ Object
Ensure all the #params are valid
139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/recog/fingerprint.rb', line 139 def verify_params(&block) return if params.empty? params.each do |param_name, pos_value| pos, value = pos_value if pos > 0 && !value.to_s.empty? yield :fail, "'#{@name}'s #{param_name} is a non-zero pos but specifies a value of '#{value}'" elsif pos == 0 && value.to_s.empty? yield :fail, "'#{@name}'s #{param_name} is not a capture (pos=0) but doesn't specify a value" end end end |
#verify_tests {|status, message| ... } ⇒ Object
Ensure all the #tests actually match the fingerprint and return the expected capture groups.
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 |
# File 'lib/recog/fingerprint.rb', line 158 def verify_tests(&block) # look for the presence of test cases if tests.size == 0 yield :warn, "'#{@name}' has no test cases" end # make sure each test case passes tests.each do |test| result = match(test.content) if result.nil? yield :fail, "'#{@name}' failed to match #{test.content.inspect} with #{@regex}'" next end = test status = :success # Ensure that all the attributes as provided by the example were parsed # out correctly and match the capture group values we expect. test.attributes.each do |k, v| next if k == '_encoding' if !result.has_key?(k) || result[k] != v = "'#{@name}' failed to find expected capture group #{k} '#{v}'. Result was #{result[k]}" status = :fail break end end yield status, end # make sure there are capture groups for all params that use them verify_tests_have_capture_groups(&block) end |
#verify_tests_have_capture_groups {|status, message| ... } ⇒ Object
For fingerprints that specify parameters that are defined by capture groups, ensure that each parameter has at least one test that defines an attribute to test for the correct capture of that parameter.
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 |
# File 'lib/recog/fingerprint.rb', line 201 def verify_tests_have_capture_groups(&block) capture_group_used = {} if !params.empty? # get a list of parameters that are defined by capture groups params.each do |param_name, pos_value| pos, value = pos_value if pos > 0 && value.to_s.empty? capture_group_used[param_name] = false end end end # match up the fingerprint parameters with test attributes tests.each do |test| test.attributes.each do |k,v| if capture_group_used.has_key?(k) capture_group_used[k] = true end end end # alert on untested parameters capture_group_used.each do |param_name, param_used| if !param_used = "'#{@name}' is missing an example that checks for parameter '#{param_name}' " + "messsage which is derived from a capture group" yield :warn, end end end |