Add rubocop rule
This commit is contained in:
@@ -26,6 +26,7 @@ require:
|
||||
- ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb
|
||||
- ./lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb
|
||||
- ./lib/rubocop/cop/lint/datastore_srvhost_usage.rb
|
||||
- ./lib/rubocop/cop/lint/bare_check_code_in_non_exploit.rb
|
||||
|
||||
Layout/SpaceBeforeBrackets:
|
||||
Enabled: true
|
||||
@@ -684,3 +685,14 @@ Lint/DetectOutdatedCmdExecApi:
|
||||
Detects outdated usage of cmd_exec with separate arguments.
|
||||
Use `create_process(executable, args: [], time_out: 15, opts: {})` API with an args array instead.
|
||||
Enabled: true
|
||||
|
||||
Lint/BareCheckCodeInNonExploit:
|
||||
Description: >-
|
||||
Use Exploit::CheckCode instead of bare CheckCode in non-exploit modules.
|
||||
Bare CheckCode will raise a NameError at runtime in auxiliary, post, and evasion modules
|
||||
because CheckCode is defined inside Msf::Exploit which is not in their ancestor chain.
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'modules/auxiliary/**/*'
|
||||
- 'modules/post/**/*'
|
||||
- 'modules/evasion/**/*'
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Lint
|
||||
# Detects usage of bare `CheckCode::*` without the `Exploit::` prefix in
|
||||
# auxiliary, post, and evasion modules.
|
||||
#
|
||||
# These modules inherit from `Msf::Auxiliary`, `Msf::Post`, or `Msf::Evasion` — not
|
||||
# `Msf::Exploit` — so bare `CheckCode::*` will raise a `NameError` at runtime because
|
||||
# `CheckCode` is defined inside `Msf::Exploit` and is not in the ancestor chain.
|
||||
#
|
||||
# @example
|
||||
# # bad - raises NameError at runtime
|
||||
# CheckCode::Safe
|
||||
# CheckCode::Vulnerable
|
||||
# CheckCode::Appears('message')
|
||||
#
|
||||
# # good
|
||||
# Exploit::CheckCode::Safe
|
||||
# Exploit::CheckCode::Vulnerable
|
||||
# Exploit::CheckCode::Appears('message')
|
||||
#
|
||||
# # also acceptable
|
||||
# Msf::Exploit::CheckCode::Safe
|
||||
class BareCheckCodeInNonExploit < Base
|
||||
extend AutoCorrector
|
||||
|
||||
MSG = 'Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. ' \
|
||||
'Bare `CheckCode` will raise a NameError at runtime.'
|
||||
|
||||
# Matches bare `CheckCode::Something` (e.g. CheckCode::Safe)
|
||||
# but NOT `Exploit::CheckCode::Something` or `Msf::Exploit::CheckCode::Something`
|
||||
def_node_matcher :bare_check_code?, <<~PATTERN
|
||||
(const
|
||||
(const nil? :CheckCode) _)
|
||||
PATTERN
|
||||
|
||||
# Matches bare `CheckCode::Something(...)` method calls.
|
||||
# e.g. CheckCode::Appears('msg') parses as:
|
||||
# (send (const nil :CheckCode) :Appears (str "msg"))
|
||||
def_node_matcher :bare_check_code_call?, <<~PATTERN
|
||||
(send
|
||||
(const nil? :CheckCode) ...)
|
||||
PATTERN
|
||||
|
||||
def on_const(node)
|
||||
return unless in_non_exploit_module?(node)
|
||||
return unless bare_check_code?(node)
|
||||
# Don't flag if the parent is already a higher const (avoid double-flagging)
|
||||
return if node.parent&.const_type? && node.parent.children.first == node
|
||||
|
||||
add_offense(node) do |corrector|
|
||||
check_code_const = find_root_const(node)
|
||||
corrector.insert_before(check_code_const, 'Exploit::') if check_code_const
|
||||
end
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless in_non_exploit_module?(node)
|
||||
return unless bare_check_code_call?(node)
|
||||
|
||||
add_offense(node.receiver) do |corrector|
|
||||
check_code_const = find_root_const(node.receiver)
|
||||
corrector.insert_before(check_code_const, 'Exploit::') if check_code_const
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Walk up the AST to find if we're inside a class that inherits from
|
||||
# Msf::Auxiliary, Msf::Post, or Msf::Evasion
|
||||
def in_non_exploit_module?(node)
|
||||
node.each_ancestor(:class).any? do |class_node|
|
||||
superclass = class_node.parent_class
|
||||
next false unless superclass
|
||||
|
||||
superclass_name = superclass.source
|
||||
superclass_name.match?(/\bMsf::(Auxiliary|Post|Evasion)\b/)
|
||||
end
|
||||
end
|
||||
|
||||
# Find the root const node in a const chain (the leftmost constant)
|
||||
def find_root_const(node)
|
||||
current = node
|
||||
while current.const_type? && current.children.first&.const_type?
|
||||
current = current.children.first
|
||||
end
|
||||
current
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,180 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop/cop/lint/bare_check_code_in_non_exploit'
|
||||
require 'rubocop/rspec/support'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Lint::BareCheckCodeInNonExploit, :config do
|
||||
subject(:cop) { described_class.new(config) }
|
||||
|
||||
let(:config) { RuboCop::Config.new }
|
||||
|
||||
context 'in an auxiliary module' do
|
||||
it 'registers an offense for bare CheckCode::Safe' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
CheckCode::Safe
|
||||
^^^^^^^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'registers an offense for bare CheckCode::Unknown' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
CheckCode::Unknown
|
||||
^^^^^^^^^^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'registers an offense for bare CheckCode::Appears with a message argument' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
CheckCode::Appears('Version is vulnerable')
|
||||
^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Appears('Version is vulnerable')
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'registers an offense for bare CheckCode::Vulnerable with details kwarg' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
CheckCode::Vulnerable(details: { version: '1.0' })
|
||||
^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Vulnerable(details: { version: '1.0' })
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register an offense for Exploit::CheckCode::Safe' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register an offense for Exploit::CheckCode::Safe' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register an offense for Exploit::CheckCode::Appears with message' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
def check
|
||||
Exploit::CheckCode::Appears('Version is vulnerable')
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'in a post module' do
|
||||
it 'registers an offense for bare CheckCode::Safe' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Post
|
||||
def check
|
||||
CheckCode::Safe
|
||||
^^^^^^^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Post
|
||||
def check
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'in an evasion module' do
|
||||
it 'registers an offense for bare CheckCode::Safe' do
|
||||
expect_offense(<<~RUBY)
|
||||
class MetasploitModule < Msf::Evasion
|
||||
def check
|
||||
CheckCode::Safe
|
||||
^^^^^^^^^^^^^^^ Lint/BareCheckCodeInNonExploit: Use `Exploit::CheckCode` instead of bare `CheckCode` in non-exploit modules. Bare `CheckCode` will raise a NameError at runtime.
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
expect_correction(<<~RUBY)
|
||||
class MetasploitModule < Msf::Evasion
|
||||
def check
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'in an exploit module' do
|
||||
it 'does not register an offense for bare CheckCode::Safe' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class MetasploitModule < Msf::Exploit
|
||||
def check
|
||||
CheckCode::Safe
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'outside a module class' do
|
||||
it 'does not register an offense for bare CheckCode::Safe' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
CheckCode::Safe
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user