metasploit-framework/spec/support/lib/module_validation.rb

185 lines
5.1 KiB
Ruby

require 'active_model'
module ModuleValidation
# Checks if values within arrays included within the passed list of acceptable values
class ArrayInclusionValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Array)
record.errors.add(attribute, "#{attribute} must be an array")
return
end
invalid_options = value - options[:in]
message = "contains invalid values #{invalid_options.inspect} - only #{options[:in].inspect} is allowed"
if invalid_options.any?
record.errors.add(attribute, :array_inclusion, message: message, value: value)
end
end
end
# Validates module metadata
class Validator < SimpleDelegator
include ActiveModel::Validations
validate :validate_filename_is_snake_case
validate :validate_reference_ctx_id
validate :validate_author_bad_chars
validate :validate_target_platforms
attr_reader :mod
def initialize(mod)
super
@mod = mod
end
#
# Acceptable Stability ratings
#
VALID_STABILITY_VALUES = [
Msf::CRASH_SAFE,
Msf::CRASH_SERVICE_RESTARTS,
Msf::CRASH_SERVICE_DOWN,
Msf::CRASH_OS_RESTARTS,
Msf::CRASH_OS_DOWN,
Msf::SERVICE_RESOURCE_LOSS,
Msf::OS_RESOURCE_LOSS
]
#
# Acceptable Side-effect ratings
#
VALID_SIDE_EFFECT_VALUES = [
Msf::ARTIFACTS_ON_DISK,
Msf::CONFIG_CHANGES,
Msf::IOC_IN_LOGS,
Msf::ACCOUNT_LOCKOUTS,
Msf::SCREEN_EFFECTS,
Msf::AUDIO_EFFECTS,
Msf::PHYSICAL_EFFECTS
]
#
# Acceptable Reliability ratings
#
VALID_RELIABILITY_VALUES = [
Msf::FIRST_ATTEMPT_FAIL,
Msf::REPEATABLE_SESSION,
Msf::UNRELIABLE_SESSION
]
#
# Acceptable site references
#
VALID_REFERENCE_CTX_ID_VALUES = %w[
CVE
CWE
BID
MSB
EDB
US-CERT-VU
ZDI
URL
WPVDB
PACKETSTORM
LOGO
SOUNDTRACK
OSVDB
VTS
OVE
]
def validate_notes_values_are_arrays
notes.each do |k, v|
unless v.is_a?(Array)
errors.add :notes, "note value #{k.inspect} must be an array, got #{v.inspect}"
end
end
end
def validate_crash_safe_not_present_in_stability_notes
if rank == Msf::ExcellentRanking && !stability.include?(Msf::CRASH_SAFE)
errors.add :stability, "must have CRASH_SAFE value if module has an ExcellentRanking, instead found #{stability.inspect}"
end
end
def validate_filename_is_snake_case
unless file_path.split('/').last.match?(/^[a-z0-9]+(?:_[a-z0-9]+)*\.rb$/)
errors.add :file_path, "must be snake case, instead found #{file_path.inspect}"
end
end
def validate_reference_ctx_id
references_ctx_id_list = references.map(&:ctx_id)
invalid_references = references_ctx_id_list - VALID_REFERENCE_CTX_ID_VALUES
invalid_references.each do |ref|
if ref.casecmp?('NOCVE')
errors.add :references, "#{ref} please include NOCVE values in the 'notes' section, rather than in 'references'"
elsif ref.casecmp?('AKA')
errors.add :references, "#{ref} please include AKA values in the 'notes' section, rather than in 'references'"
else
errors.add :references, "#{ref} is not valid, must be in #{VALID_REFERENCE_CTX_ID_VALUES}"
end
end
end
def validate_author_bad_chars
author.each do |i|
if i.name =~ /^@.+$/
errors.add :author, "must not include username handles, found #{i.name.inspect}. Try leaving it in a comment instead"
end
end
end
def validate_target_platforms
if platform.blank? && type == 'exploit'
targets.each do |target|
if target.platform.blank?
errors.add :platform, 'must be included either within targets or platform module metadata'
end
end
end
end
def has_notes?
!notes.empty?
end
validates :mod, presence: true
with_options if: :has_notes? do |mod|
mod.validate :validate_crash_safe_not_present_in_stability_notes
mod.validate :validate_notes_values_are_arrays
mod.validates :stability,
'module_validation/array_inclusion': { in: VALID_STABILITY_VALUES }
mod.validates :side_effects,
'module_validation/array_inclusion': { in: VALID_SIDE_EFFECT_VALUES }
mod.validates :reliability,
'module_validation/array_inclusion': { in: VALID_RELIABILITY_VALUES }
end
validates :license,
presence: true,
inclusion: { in: LICENSES, message: 'must include a valid license' }
validates :rank,
presence: true,
inclusion: { in: Msf::RankingName.keys, message: 'must include a valid module ranking' }
validates :author,
presence: true
validates :name,
presence: true,
format: { with: /\A[^&<>]+\z/, message: 'must not contain the characters &<>' }
validates :description,
presence: true
end
end