Skip to content

Commit fe99f4b

Browse files
author
Tod Beardsley
committed
Land rapid7#3712, a nicer exploit-checker for msftidy
2 parents 49adde2 + 073c668 commit fe99f4b

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

tools/dev/pre-commit-hook.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ def merge_error_message
5050

5151
changed_files.each_line do |fname|
5252
fname.strip!
53-
next unless File.exist?(fname) and File.file?(fname)
54-
next unless fname =~ /modules.+\.rb/
53+
next unless File.exist?(fname)
54+
next unless File.file?(fname)
55+
next unless fname =~ /^modules.+\.rb/
5556
files_to_check << fname
5657
end
5758

tools/msftidy.rb

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
CHECK_OLD_RUBIES = !!ENV['MSF_CHECK_OLD_RUBIES']
1414
SUPPRESS_INFO_MESSAGES = !!ENV['MSF_SUPPRESS_INFO_MESSAGES']
15-
ENCODING_REGEX = /^# (?:\-\*\- )?encoding:\s*(\S+)/
1615

1716
if CHECK_OLD_RUBIES
1817
require 'rvm'
@@ -48,11 +47,16 @@ class Msftidy
4847
WARNINGS = 0x10
4948
ERRORS = 0x20
5049

50+
# Some compiles regexes
51+
REGEX_MSF_EXPLOIT = / \< Msf::Exploit/
52+
REGEX_IS_BLANK_OR_END = /^\s*end\s*$/
53+
5154
attr_reader :full_filepath, :source, :stat, :name, :status
5255

5356
def initialize(source_file)
5457
@full_filepath = source_file
5558
@source = load_file(source_file)
59+
@lines = @source.lines # returns an enumerator
5660
@status = OK
5761
@name = File.basename(source_file)
5862
end
@@ -110,29 +114,8 @@ def check_mode
110114
end
111115
end
112116

113-
# Check that modules don't have any encoding comment and that
114-
# non-modules have an explicity binary encoding comment
115-
def check_encoding
116-
# coding/encoding lines must be the first or second line if present
117-
encoding_lines = @source.lines.to_a[0,2].select { |l| l =~ ENCODING_REGEX }
118-
if @full_filepath =~ /(?:^|\/)modules\//
119-
warn('Modules do not need an encoding comment') unless encoding_lines.empty?
120-
else
121-
if encoding_lines.empty?
122-
warn('Non-modules must have an encoding comment')
123-
else
124-
encoding_line = encoding_lines.first
125-
encoding_line =~ ENCODING_REGEX
126-
encoding_type = Regexp.last_match(1)
127-
unless encoding_type == 'binary'
128-
warn("Non-modules must have a binary encoding comment, not #{encoding_type}")
129-
end
130-
end
131-
end
132-
end
133-
134117
def check_shebang
135-
if @source.lines.first =~ /^#!/
118+
if @lines.first =~ /^#!/
136119
warn("Module should not have a #! line")
137120
end
138121
end
@@ -148,7 +131,7 @@ def check_nokogiri
148131
msg = "Using Nokogiri in modules can be risky, use REXML instead."
149132
has_nokogiri = false
150133
has_nokogiri_xml_parser = false
151-
@source.each_line do |line|
134+
@lines.each do |line|
152135
if has_nokogiri
153136
if line =~ /Nokogiri::XML\.parse/ or line =~ /Nokogiri::XML::Reader/
154137
has_nokogiri_xml_parser = true
@@ -165,7 +148,7 @@ def check_ref_identifiers
165148
in_super = false
166149
in_refs = false
167150

168-
@source.each_line do |line|
151+
@lines.each do |line|
169152
if !in_super and line =~ /\s+super\(/
170153
in_super = true
171154
elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/
@@ -225,7 +208,7 @@ def check_ref_identifiers
225208
# warn if so. Since Ruby 1.9 this has not been necessary and
226209
# the framework only suports 1.9+
227210
def check_rubygems
228-
@source.each_line do |line|
211+
@lines.each do |line|
229212
if line_has_require?(line, 'rubygems')
230213
warn("Explicitly requiring/loading rubygems is not necessary")
231214
break
@@ -256,7 +239,7 @@ def check_old_keywords
256239
max_count = 10
257240
counter = 0
258241
if @source =~ /^##/
259-
@source.each_line do |line|
242+
@lines.each do |line|
260243
# If exists, the $Id$ keyword should appear at the top of the code.
261244
# If not (within the first 10 lines), then we assume there's no
262245
# $Id$, and then bail.
@@ -288,7 +271,7 @@ def check_badchars
288271
in_super = false
289272
in_author = false
290273

291-
@source.each_line do |line|
274+
@lines.each do |line|
292275
#
293276
# Mark our "super" code block
294277
#
@@ -366,8 +349,37 @@ def check_old_rubies
366349
error("Fails alternate Ruby version check") if rubies.size != res.size
367350
end
368351

352+
def is_exploit_module?
353+
ret = false
354+
if @source =~ REGEX_MSF_EXPLOIT
355+
# having Msf::Exploit is good indicator, but will false positive on
356+
# specs and other files containing the string, but not really acting
357+
# as exploit modules, so here we check the file for some actual contents
358+
# this could be done in a simpler way, but this let's us add more later
359+
msf_exploit_line_no = nil
360+
@lines.each_with_index do |line, idx|
361+
if line = REGEX_MSF_EXPLOIT
362+
# note the line number
363+
msf_exploit_line_no = idx
364+
elsif msf_exploit_line_no
365+
# check there is anything but empty space between here and the next end
366+
# something more complex could be added here
367+
if line !~ REGEX_IS_BLANK_OR_END
368+
# if the line is not 'end' and is not blank, prolly exploit module
369+
ret = true
370+
break
371+
else
372+
# then keep checking in case there are more than one Msf::Exploit
373+
msf_exploit_line_no = nil
374+
end
375+
end
376+
end
377+
end
378+
ret
379+
end
380+
369381
def check_ranking
370-
return if @source !~ / \< Msf::Exploit/
382+
return unless is_exploit_module?
371383

372384
available_ranks = [
373385
'ManualRanking',
@@ -406,7 +418,7 @@ def check_disclosure_date
406418
error('Incorrect disclosure date format')
407419
end
408420
else
409-
error('Exploit is missing a disclosure date') if @source =~ / \< Msf::Exploit/
421+
error('Exploit is missing a disclosure date') if is_exploit_module?
410422
end
411423
end
412424

@@ -462,7 +474,7 @@ def check_lines
462474
src_ended = false
463475
idx = 0
464476

465-
@source.each_line { |ln|
477+
@lines.each do |ln|
466478
idx += 1
467479

468480
# block comment awareness
@@ -541,7 +553,7 @@ def check_lines
541553
if ln =~ /^\s*Rank\s*=\s*/ and @source =~ /<\sMsf::Auxiliary/
542554
warn("Auxiliary modules have no 'Rank': #{ln}", idx)
543555
end
544-
}
556+
end
545557
end
546558

547559
def check_vuln_codes
@@ -605,7 +617,6 @@ def run_checks(full_filepath)
605617
tidy = Msftidy.new(full_filepath)
606618
tidy.check_mode
607619
tidy.check_shebang
608-
tidy.check_encoding
609620
tidy.check_nokogiri
610621
tidy.check_rubygems
611622
tidy.check_ref_identifiers

0 commit comments

Comments
 (0)