Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/support/lib/module_validation.rb
19758 views
1
require 'active_model'
2
3
module ModuleValidation
4
# Checks if values within arrays included within the passed list of acceptable values
5
class ArrayInclusionValidator < ActiveModel::EachValidator
6
def validate_each(record, attribute, value)
7
unless value.is_a?(Array)
8
record.errors.add(attribute, "#{attribute} must be an array")
9
return
10
end
11
12
# Special cases for modules/exploits/bsd/finger/morris_fingerd_bof.rb which has a one-off architecture defined in
13
# the module itself, and that value is not included in the valid list of architectures.
14
# https://github.com/rapid7/metasploit-framework/blob/389d84cbf0d7c58727846466d9a9f6a468f32c61/modules/exploits/bsd/finger/morris_fingerd_bof.rb#L11
15
return if attribute == :arch && value == ["vax"] && record.fullname == "exploit/bsd/finger/morris_fingerd_bof"
16
return if value == options[:sentinel_value]
17
18
invalid_options = value - options[:in]
19
message = "contains invalid values #{invalid_options.inspect} - only #{options[:in].inspect} is allowed"
20
21
if invalid_options.any?
22
record.errors.add(attribute, :array_inclusion, message: message, value: value)
23
end
24
end
25
end
26
27
# Validates module metadata
28
class Validator < SimpleDelegator
29
include ActiveModel::Validations
30
31
validate :validate_filename_is_snake_case
32
validate :validate_reference_ctx_id
33
validate :validate_author_bad_chars
34
validate :validate_target_platforms
35
validate :validate_description_does_not_contain_non_printable_chars
36
validate :validate_name_does_not_contain_non_printable_chars
37
validate :validate_attack_reference_format
38
39
attr_reader :mod
40
41
def initialize(mod)
42
super
43
@mod = mod
44
end
45
46
#
47
# Acceptable Stability ratings
48
#
49
VALID_STABILITY_VALUES = [
50
Msf::CRASH_SAFE,
51
Msf::CRASH_SERVICE_RESTARTS,
52
Msf::CRASH_SERVICE_DOWN,
53
Msf::CRASH_OS_RESTARTS,
54
Msf::CRASH_OS_DOWN,
55
Msf::SERVICE_RESOURCE_LOSS,
56
Msf::OS_RESOURCE_LOSS
57
]
58
59
#
60
# Acceptable Side-effect ratings
61
#
62
VALID_SIDE_EFFECT_VALUES = [
63
Msf::ARTIFACTS_ON_DISK,
64
Msf::CONFIG_CHANGES,
65
Msf::IOC_IN_LOGS,
66
Msf::ACCOUNT_LOCKOUTS,
67
Msf::ACCOUNT_LOGOUT,
68
Msf::SCREEN_EFFECTS,
69
Msf::AUDIO_EFFECTS,
70
Msf::PHYSICAL_EFFECTS
71
]
72
73
#
74
# Acceptable Reliability ratings
75
#
76
VALID_RELIABILITY_VALUES = [
77
Msf::FIRST_ATTEMPT_FAIL,
78
Msf::REPEATABLE_SESSION,
79
Msf::UNRELIABLE_SESSION,
80
Msf::EVENT_DEPENDENT
81
]
82
83
#
84
# Acceptable site references
85
#
86
VALID_REFERENCE_CTX_ID_VALUES = %w[
87
ATT&CK
88
CVE
89
CWE
90
BID
91
MSB
92
EDB
93
US-CERT-VU
94
ZDI
95
URL
96
WPVDB
97
PACKETSTORM
98
LOGO
99
SOUNDTRACK
100
OSVDB
101
VTS
102
OVE
103
]
104
105
def validate_notes_values_are_arrays
106
notes.each do |k, v|
107
unless v.is_a?(Array)
108
errors.add :notes, "note value #{k.inspect} must be an array, got #{v.inspect}"
109
end
110
end
111
end
112
113
def validate_crash_safe_not_present_in_stability_notes
114
if rank == Msf::ExcellentRanking && !stability.include?(Msf::CRASH_SAFE)
115
return if stability == Msf::UNKNOWN_STABILITY
116
117
errors.add :stability, "must have CRASH_SAFE value if module has an ExcellentRanking, instead found #{stability.inspect}"
118
end
119
end
120
121
def validate_filename_is_snake_case
122
unless file_path.split('/').last.match?(/^[a-z0-9]+(?:_[a-z0-9]+)*\.rb$/)
123
errors.add :file_path, "must be snake case, instead found #{file_path.inspect}"
124
end
125
end
126
127
def validate_reference_ctx_id
128
references_ctx_id_list = references.map(&:ctx_id)
129
invalid_references = references_ctx_id_list - VALID_REFERENCE_CTX_ID_VALUES
130
131
invalid_references.each do |ref|
132
if ref.casecmp?('NOCVE')
133
errors.add :references, "#{ref} please include NOCVE values in the 'notes' section, rather than in 'references'"
134
elsif ref.casecmp?('AKA')
135
errors.add :references, "#{ref} please include AKA values in the 'notes' section, rather than in 'references'"
136
else
137
errors.add :references, "#{ref} is not valid, must be in #{VALID_REFERENCE_CTX_ID_VALUES}"
138
end
139
end
140
end
141
142
def validate_author_bad_chars
143
author.each do |i|
144
if i.name =~ /^@.+$/
145
errors.add :author, "must not include username handles, found #{i.name.inspect}. Try leaving it in a comment instead"
146
end
147
end
148
end
149
150
def validate_target_platforms
151
if platform.blank? && type == 'exploit'
152
targets.each do |target|
153
if target.platform.blank?
154
errors.add :platform, 'must be included either within targets or platform module metadata'
155
end
156
end
157
end
158
end
159
160
def validate_attack_reference_format
161
references.each do |ref|
162
next unless ref.respond_to?(:ctx_id) && ref.respond_to?(:ctx_val)
163
next unless ref.ctx_id == 'ATT&CK'
164
165
val = ref.ctx_val
166
prefix = val[/\A[A-Z]+/]
167
valid_format = Msf::Mitre::Attack::Categories::PATHS.key?(prefix) && val.match?(/\A#{prefix}[\d.]+\z/)
168
whitespace = val.match?(/\s/)
169
170
unless valid_format && !whitespace
171
errors.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."
172
end
173
end
174
end
175
176
def has_notes?
177
!notes.empty?
178
end
179
180
def validate_description_does_not_contain_non_printable_chars
181
unless description&.match?(/\A[ -~\t\n]*\z/)
182
# Blank descriptions are validated elsewhere, so we will return early to not also add this error
183
# and cause unnecessary confusion.
184
return if description.nil?
185
186
errors.add :description, 'must only contain human-readable printable ascii characters, including newlines and tabs'
187
end
188
end
189
190
def validate_name_does_not_contain_non_printable_chars
191
unless name&.match?(/\A[ -~]+\z/)
192
errors.add :name, 'must only contain human-readable printable ascii characters'
193
end
194
end
195
196
validates :mod, presence: true
197
198
with_options if: :has_notes? do |mod|
199
mod.validate :validate_crash_safe_not_present_in_stability_notes
200
mod.validate :validate_notes_values_are_arrays
201
202
mod.validates :stability,
203
'module_validation/array_inclusion': { in: VALID_STABILITY_VALUES, sentinel_value: Msf::UNKNOWN_STABILITY }
204
205
mod.validates :side_effects,
206
'module_validation/array_inclusion': { in: VALID_SIDE_EFFECT_VALUES, sentinel_value: Msf::UNKNOWN_SIDE_EFFECTS }
207
208
mod.validates :reliability,
209
'module_validation/array_inclusion': { in: VALID_RELIABILITY_VALUES, sentinel_value: Msf::UNKNOWN_RELIABILITY }
210
end
211
212
validates :arch,
213
'module_validation/array_inclusion': { in: Rex::Arch::ARCH_TYPES }
214
215
validates :license,
216
presence: true,
217
inclusion: { in: LICENSES, message: 'must include a valid license' }
218
219
validates :rank,
220
presence: true,
221
inclusion: { in: Msf::RankingName.keys, message: 'must include a valid module ranking' }
222
223
validates :author,
224
presence: true
225
226
validates :name,
227
presence: true,
228
format: { with: /\A[^&<>]+\z/, message: 'must not contain the characters &<>' }
229
230
validates :description,
231
presence: true
232
end
233
end
234
235