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.
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 |
# File 'lib/recog/fingerprint.rb', line 158 def verify_tests(&block) if tests.size == 0 yield :warn, "'#{@name}' has no test cases" end 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 end |