Files
metasploit-gs/lib/msf/core/module/module_info.rb
T
Valentin Lobstein f41eda1128 Add GHSA and OSV reference type support
Add support for GHSA (GitHub Security Advisories) and OSV (Open Source
Vulnerabilities) as structured reference types in Metasploit modules.

Convert 49 hardcoded GHSA URLs to structured ['GHSA', 'GHSA-xxxx'] format
across existing modules, and add support for repository-specific GHSA
references with an optional third parameter ['GHSA', 'GHSA-xxxx', 'repo'].

Update reference validation, module validator, and info_fixups to handle
the new reference types correctly.
2026-02-09 15:17:23 +01:00

232 lines
5.5 KiB
Ruby

module Msf::Module::ModuleInfo
#
# CONSTANTS
#
# The list of options that don't support merging in an information hash.
UpdateableOptions = ['Name', 'Description', 'Alias', 'PayloadCompat', 'Stance'].freeze
# Reference types that can have 2 or 3 elements (e.g., GHSA with optional repo)
ReferencesWithOptionalThirdElement = ['GHSA'].freeze
#
# Instance Methods
#
#
# Returns the module's alias, if it has one. Otherwise, the module's
# name is returned.
#
def alias
module_info['Alias']
end
#
# Return the module's description.
#
def description
module_info['Description']
end
#
# Returns the disclosure date, if known.
#
def disclosure_date
date_str = Date.parse(module_info['DisclosureDate'].to_s) rescue nil
end
#
# Return the module's name from the module information hash.
#
def name
module_info['Name']
end
#
# Return the module's notes (including AKA and NOCVE descriptors).
#
def notes
module_info['Notes']
end
protected
#
# Attributes
#
# @!attribute module_info
attr_accessor :module_info
#
# Instance Methods
#
#
# Register options with a specific owning class.
#
def info_fixups
# Each reference should be an array consisting of two or three elements
refs = module_info['References']
return unless refs&.any?
refs.reject! do |ref|
next true unless ref.respond_to?('[]') && !ref.empty?
# Some reference types can have 2 or 3 elements (e.g., GHSA with optional repo)
# Other references should have 2 elements
ref_type = ref[0]
can_have_third_element = ReferencesWithOptionalThirdElement.include?(ref_type)
valid_length = can_have_third_element ? (ref.length == 2 || ref.length == 3) : (ref.length == 2)
!valid_length
end
end
#
# Checks and merges the supplied key/value pair in the supplied hash.
#
def merge_check_key(info, name, val)
merge_method = "merge_info_#{name.downcase}"
return __send__(merge_method, info, val) if respond_to?(merge_method, true)
return info[name] = val unless info[name]
# Handle hash merging recursively
if info[name].is_a?(Hash)
raise TypeError, 'can only merge a hash into a hash' unless val.is_a?(Hash)
val.each_pair { |val_key, val_val| merge_check_key(info[name], val_key, val_val) }
return
end
# Convert to array if needed
info[name] = Array(info[name]) unless info[name].is_a?(Array)
# Merge values, avoiding duplicates
values_to_add = val.is_a?(Array) ? val : [val]
values_to_add.each { |v| info[name] << v unless info[name].include?(v) }
end
#
# Merges options in the info hash in a sane fashion, as some options
# require special attention.
#
def merge_info(info, opts)
opts.each_pair { |name, val|
merge_check_key(info, name, val)
}
info
end
#
# Merges advanced options.
#
def merge_info_advanced_options(info, val)
merge_info_options(info, val, true, false)
end
#
# Merge aliases with an underscore delimiter.
#
def merge_info_alias(info, val)
merge_info_string(info, 'Alias', val, '_')
end
#
# Merges the module description.
#
def merge_info_description(info, val)
key = 'Description'
unless info[key]
info[key] = val
return
end
current_value = Msf::Serializer::ReadableText.word_wrap_description(info[key])
new_value = Msf::Serializer::ReadableText.word_wrap_description(val)
info[key] = current_value.end_with?('.') ? "#{current_value}\n#{val}" : "#{current_value}.\n\n#{new_value}"
end
#
# Merges advanced options.
#
def merge_info_evasion_options(info, val)
merge_info_options(info, val, false, true)
end
#
# Merges the module name.
#
def merge_info_name(info, val)
merge_info_string(info, 'Name', val, ', ', true)
end
#
# Merges options.
#
def merge_info_options(info, val, advanced = false, evasion = false)
key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options'
new_cont = Msf::OptionContainer.new
new_cont.add_options(val, advanced, evasion)
cur_cont = Msf::OptionContainer.new
cur_cont.add_options(info[key_name] || [], advanced, evasion)
new_cont.each_option { |name, option|
next if (cur_cont.get(name))
info[key_name] = [] if (!info[key_name])
info[key_name] << option
}
end
#
# Merges a given key in the info hash with a delimiter.
#
def merge_info_string(info, key, val, delim = ', ', inverse = false)
if (info[key])
if (inverse == true)
info[key] = info[key] + delim + val
else
info[key] = val + delim + info[key]
end
else
info[key] = val
end
end
#
# Merge the module version.
#
def merge_info_version(info, val)
merge_info_string(info, 'Version', val)
end
#
# Updates information in the supplied info hash and merges other
# information. This method is used to override things like Name, Version,
# and Description without losing the ability to merge architectures,
# platforms, and options.
#
def update_info(info, opts)
opts.each_pair do |name, val|
# If the supplied option name is one of the ones that we should
# override by default
if UpdateableOptions.include?(name)
# Only if the entry is currently nil do we use our value
if info[name].nil?
info[name] = val
end
# Otherwise, perform the merge operation like normal
else
merge_check_key(info, name, val)
end
end
info
end
end