f41eda1128
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.
232 lines
5.5 KiB
Ruby
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
|