Add rubocop rule

This commit is contained in:
adfoster-r7
2026-04-10 12:54:06 +01:00
parent 92b200e430
commit 9a613fc249
3 changed files with 286 additions and 0 deletions
+12
View File
@@ -26,6 +26,7 @@ require:
- ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb - ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb
- ./lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb - ./lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb
- ./lib/rubocop/cop/lint/datastore_srvhost_usage.rb - ./lib/rubocop/cop/lint/datastore_srvhost_usage.rb
- ./lib/rubocop/cop/lint/bare_check_code_in_non_exploit.rb
Layout/SpaceBeforeBrackets: Layout/SpaceBeforeBrackets:
Enabled: true Enabled: true
@@ -684,3 +685,14 @@ Lint/DetectOutdatedCmdExecApi:
Detects outdated usage of cmd_exec with separate arguments. Detects outdated usage of cmd_exec with separate arguments.
Use `create_process(executable, args: [], time_out: 15, opts: {})` API with an args array instead. Use `create_process(executable, args: [], time_out: 15, opts: {})` API with an args array instead.
Enabled: true 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