Path: blob/master/spec/support/lib/module_validation.rb
19758 views
require 'active_model'12module ModuleValidation3# Checks if values within arrays included within the passed list of acceptable values4class ArrayInclusionValidator < ActiveModel::EachValidator5def validate_each(record, attribute, value)6unless value.is_a?(Array)7record.errors.add(attribute, "#{attribute} must be an array")8return9end1011# Special cases for modules/exploits/bsd/finger/morris_fingerd_bof.rb which has a one-off architecture defined in12# the module itself, and that value is not included in the valid list of architectures.13# https://github.com/rapid7/metasploit-framework/blob/389d84cbf0d7c58727846466d9a9f6a468f32c61/modules/exploits/bsd/finger/morris_fingerd_bof.rb#L1114return if attribute == :arch && value == ["vax"] && record.fullname == "exploit/bsd/finger/morris_fingerd_bof"15return if value == options[:sentinel_value]1617invalid_options = value - options[:in]18message = "contains invalid values #{invalid_options.inspect} - only #{options[:in].inspect} is allowed"1920if invalid_options.any?21record.errors.add(attribute, :array_inclusion, message: message, value: value)22end23end24end2526# Validates module metadata27class Validator < SimpleDelegator28include ActiveModel::Validations2930validate :validate_filename_is_snake_case31validate :validate_reference_ctx_id32validate :validate_author_bad_chars33validate :validate_target_platforms34validate :validate_description_does_not_contain_non_printable_chars35validate :validate_name_does_not_contain_non_printable_chars36validate :validate_attack_reference_format3738attr_reader :mod3940def initialize(mod)41super42@mod = mod43end4445#46# Acceptable Stability ratings47#48VALID_STABILITY_VALUES = [49Msf::CRASH_SAFE,50Msf::CRASH_SERVICE_RESTARTS,51Msf::CRASH_SERVICE_DOWN,52Msf::CRASH_OS_RESTARTS,53Msf::CRASH_OS_DOWN,54Msf::SERVICE_RESOURCE_LOSS,55Msf::OS_RESOURCE_LOSS56]5758#59# Acceptable Side-effect ratings60#61VALID_SIDE_EFFECT_VALUES = [62Msf::ARTIFACTS_ON_DISK,63Msf::CONFIG_CHANGES,64Msf::IOC_IN_LOGS,65Msf::ACCOUNT_LOCKOUTS,66Msf::ACCOUNT_LOGOUT,67Msf::SCREEN_EFFECTS,68Msf::AUDIO_EFFECTS,69Msf::PHYSICAL_EFFECTS70]7172#73# Acceptable Reliability ratings74#75VALID_RELIABILITY_VALUES = [76Msf::FIRST_ATTEMPT_FAIL,77Msf::REPEATABLE_SESSION,78Msf::UNRELIABLE_SESSION,79Msf::EVENT_DEPENDENT80]8182#83# Acceptable site references84#85VALID_REFERENCE_CTX_ID_VALUES = %w[86ATT&CK87CVE88CWE89BID90MSB91EDB92US-CERT-VU93ZDI94URL95WPVDB96PACKETSTORM97LOGO98SOUNDTRACK99OSVDB100VTS101OVE102]103104def validate_notes_values_are_arrays105notes.each do |k, v|106unless v.is_a?(Array)107errors.add :notes, "note value #{k.inspect} must be an array, got #{v.inspect}"108end109end110end111112def validate_crash_safe_not_present_in_stability_notes113if rank == Msf::ExcellentRanking && !stability.include?(Msf::CRASH_SAFE)114return if stability == Msf::UNKNOWN_STABILITY115116errors.add :stability, "must have CRASH_SAFE value if module has an ExcellentRanking, instead found #{stability.inspect}"117end118end119120def validate_filename_is_snake_case121unless file_path.split('/').last.match?(/^[a-z0-9]+(?:_[a-z0-9]+)*\.rb$/)122errors.add :file_path, "must be snake case, instead found #{file_path.inspect}"123end124end125126def validate_reference_ctx_id127references_ctx_id_list = references.map(&:ctx_id)128invalid_references = references_ctx_id_list - VALID_REFERENCE_CTX_ID_VALUES129130invalid_references.each do |ref|131if ref.casecmp?('NOCVE')132errors.add :references, "#{ref} please include NOCVE values in the 'notes' section, rather than in 'references'"133elsif ref.casecmp?('AKA')134errors.add :references, "#{ref} please include AKA values in the 'notes' section, rather than in 'references'"135else136errors.add :references, "#{ref} is not valid, must be in #{VALID_REFERENCE_CTX_ID_VALUES}"137end138end139end140141def validate_author_bad_chars142author.each do |i|143if i.name =~ /^@.+$/144errors.add :author, "must not include username handles, found #{i.name.inspect}. Try leaving it in a comment instead"145end146end147end148149def validate_target_platforms150if platform.blank? && type == 'exploit'151targets.each do |target|152if target.platform.blank?153errors.add :platform, 'must be included either within targets or platform module metadata'154end155end156end157end158159def validate_attack_reference_format160references.each do |ref|161next unless ref.respond_to?(:ctx_id) && ref.respond_to?(:ctx_val)162next unless ref.ctx_id == 'ATT&CK'163164val = ref.ctx_val165prefix = val[/\A[A-Z]+/]166valid_format = Msf::Mitre::Attack::Categories::PATHS.key?(prefix) && val.match?(/\A#{prefix}[\d.]+\z/)167whitespace = val.match?(/\s/)168169unless valid_format && !whitespace170errors.add :references, "ATT&CK reference '#{val}' is invalid. Must start with one of #{Msf::Mitre::Attack::Categories::PATHS.keys.inspect} and be followed by digits/periods, no whitespace."171end172end173end174175def has_notes?176!notes.empty?177end178179def validate_description_does_not_contain_non_printable_chars180unless description&.match?(/\A[ -~\t\n]*\z/)181# Blank descriptions are validated elsewhere, so we will return early to not also add this error182# and cause unnecessary confusion.183return if description.nil?184185errors.add :description, 'must only contain human-readable printable ascii characters, including newlines and tabs'186end187end188189def validate_name_does_not_contain_non_printable_chars190unless name&.match?(/\A[ -~]+\z/)191errors.add :name, 'must only contain human-readable printable ascii characters'192end193end194195validates :mod, presence: true196197with_options if: :has_notes? do |mod|198mod.validate :validate_crash_safe_not_present_in_stability_notes199mod.validate :validate_notes_values_are_arrays200201mod.validates :stability,202'module_validation/array_inclusion': { in: VALID_STABILITY_VALUES, sentinel_value: Msf::UNKNOWN_STABILITY }203204mod.validates :side_effects,205'module_validation/array_inclusion': { in: VALID_SIDE_EFFECT_VALUES, sentinel_value: Msf::UNKNOWN_SIDE_EFFECTS }206207mod.validates :reliability,208'module_validation/array_inclusion': { in: VALID_RELIABILITY_VALUES, sentinel_value: Msf::UNKNOWN_RELIABILITY }209end210211validates :arch,212'module_validation/array_inclusion': { in: Rex::Arch::ARCH_TYPES }213214validates :license,215presence: true,216inclusion: { in: LICENSES, message: 'must include a valid license' }217218validates :rank,219presence: true,220inclusion: { in: Msf::RankingName.keys, message: 'must include a valid module ranking' }221222validates :author,223presence: true224225validates :name,226presence: true,227format: { with: /\A[^&<>]+\z/, message: 'must not contain the characters &<>' }228229validates :description,230presence: true231end232end233234235