Skip to content

Commit ee6d594

Browse files
Merge pull request #20777 from rapid7/revert-20424-enh/MS-9930/vuln_report
Revert "Vulnerability Report Enhancement"
2 parents 3936fc7 + c355372 commit ee6d594

File tree

17 files changed

+101
-1376
lines changed

17 files changed

+101
-1376
lines changed

Gemfile.lock

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,15 +353,12 @@ GEM
353353
mutex_m
354354
railties (~> 7.0)
355355
metasploit-payloads (2.0.237)
356-
metasploit_data_models (6.0.11)
356+
metasploit_data_models (6.0.9)
357357
activerecord (~> 7.0)
358358
activesupport (~> 7.0)
359359
arel-helpers
360-
bigdecimal
361-
drb
362360
metasploit-concern
363361
metasploit-model (>= 3.1)
364-
mutex_m
365362
pg
366363
railties (~> 7.0)
367364
recog

db/schema.rb

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[7.2].define(version: 2025_07_21_114306) do
13+
ActiveRecord::Schema[7.2].define(version: 2025_02_04_172657) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "plpgsql"
1616

@@ -521,16 +521,6 @@
521521
t.string "netmask"
522522
end
523523

524-
create_table "service_links", force: :cascade do |t|
525-
t.bigint "parent_id", null: false
526-
t.bigint "child_id", null: false
527-
t.datetime "created_at", null: false
528-
t.datetime "updated_at", null: false
529-
t.index ["child_id"], name: "index_service_links_on_child_id"
530-
t.index ["parent_id", "child_id"], name: "index_service_links_on_parent_id_and_child_id", unique: true
531-
t.index ["parent_id"], name: "index_service_links_on_parent_id"
532-
end
533-
534524
create_table "services", id: :serial, force: :cascade do |t|
535525
t.integer "host_id"
536526
t.datetime "created_at", precision: nil
@@ -540,8 +530,7 @@
540530
t.string "name"
541531
t.datetime "updated_at", precision: nil
542532
t.text "info"
543-
t.jsonb "resource", default: {}, null: false
544-
t.index ["host_id", "port", "proto", "name", "resource"], name: "index_services_on_5_columns", unique: true
533+
t.index ["host_id", "port", "proto"], name: "index_services_on_host_id_and_port_and_proto", unique: true
545534
t.index ["name"], name: "index_services_on_name"
546535
t.index ["port"], name: "index_services_on_port"
547536
t.index ["proto"], name: "index_services_on_proto"
@@ -697,7 +686,6 @@
697686
t.integer "vuln_attempt_count", default: 0
698687
t.integer "origin_id"
699688
t.string "origin_type"
700-
t.jsonb "resource", default: {}, null: false
701689
t.index ["name"], name: "index_vulns_on_name"
702690
t.index ["origin_id"], name: "index_vulns_on_origin_id"
703691
end
@@ -815,7 +803,4 @@
815803
t.boolean "limit_to_network", default: false, null: false
816804
t.boolean "import_fingerprint", default: false
817805
end
818-
819-
add_foreign_key "service_links", "services", column: "child_id"
820-
add_foreign_key "service_links", "services", column: "parent_id"
821806
end

lib/msf/core/db_manager/service.rb

Lines changed: 7 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@ def delete_service(opts)
66
::ApplicationRecord.connection_pool.with_connection {
77
deleted = []
88
opts[:ids].each do |service_id|
9-
begin
10-
service = Mdm::Service.find(service_id)
11-
rescue ActiveRecord::RecordNotFound
12-
# This happens when the service was the child of another service we have already deleted
13-
# Deletion of children is automatic via dependent: :destroy on the association
14-
dlog("Service with id #{service_id} already deleted")
15-
next
16-
end
9+
service = Mdm::Service.find(service_id)
1710
begin
1811
deleted << service.destroy
1912
rescue
@@ -50,15 +43,11 @@ def find_or_create_service(opts)
5043
# +:workspace+:: the workspace for the service
5144
#
5245
# opts may contain
53-
# +:name+:: the application layer protocol (e.g. ssh, mssql, smb)
54-
# +:sname+:: an alias for the above
55-
# +:info+:: detailed information about the service such as name and version information
56-
# +:state+:: the current listening state of the service (one of: open, closed, filtered, unknown)
57-
# +:resource+:: the resource this service is associated with, such as a a DN for an an LDAP object
58-
# base URI for a web application, pipe name for DCERPC service, etc.
59-
# +:parents+:: a single service Hash or an Array of service Hash representing the parent services this
60-
# service is associated with, such as a HTTP service for a web application.
61-
#`
46+
# +:name+:: the application layer protocol (e.g. ssh, mssql, smb)
47+
# +:sname+:: an alias for the above
48+
# +:info+:: Detailed information about the service such as name and version information
49+
# +:state+:: The current listening state of the service (one of: open, closed, filtered, unknown)
50+
#
6251
# @return [Mdm::Service,nil]
6352
def report_service(opts)
6453
return if !active
@@ -80,7 +69,6 @@ def report_service(opts)
8069
if opts[:sname]
8170
opts[:name] = opts.delete(:sname)
8271
end
83-
opts[:name] = opts[:name].to_s.downcase if opts[:name]
8472

8573
if addr.kind_of? ::Mdm::Host
8674
host = addr
@@ -96,14 +84,7 @@ def report_service(opts)
9684

9785
proto = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO
9886

99-
sopts = {
100-
port: opts[:port].to_i,
101-
proto: proto
102-
}
103-
sopts[:name] = opts[:name] if opts[:name]
104-
sopts[:resource] = opts[:resource] if opts[:resource]
105-
service = host.services.where(sopts).first_or_initialize
106-
87+
service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_initialize
10788
ostate = service.state
10889
opts.each { |k,v|
10990
if (service.attribute_names.include?(k.to_s))
@@ -112,15 +93,8 @@ def report_service(opts)
11293
dlog("Unknown attribute for Service: #{k}")
11394
end
11495
}
115-
11696
service.state ||= Msf::ServiceState::Open
11797
service.info ||= ""
118-
parents = process_service_chain(host, opts.delete(:parents)) if opts[:parents]
119-
if parents
120-
parents.each do |parent|
121-
service.parents << parent if parent && !service.parents.include?(parent)
122-
end
123-
end
12498

12599
begin
126100
framework.events.on_db_service(service) if service.new_record?
@@ -189,44 +163,4 @@ def update_service(opts)
189163
return service
190164
}
191165
end
192-
193-
def process_service_chain(host, services)
194-
return if services.nil? || host.nil?
195-
return unless services.is_a?(Hash) || services.is_a?(::Array)
196-
return unless host.is_a?(Mdm::Host)
197-
198-
services = [services] unless services.is_a?(Array)
199-
services.map do |service|
200-
return unless service.is_a?(Hash)
201-
return if service[:port].nil? || service[:proto].nil?
202-
203-
parents = nil
204-
if service[:parents]&.any?
205-
parents = process_service_chain(host, service[:parents])
206-
end
207-
208-
service_info = {
209-
port: service[:port].to_i,
210-
proto: service[:proto].to_s.downcase,
211-
}
212-
service_info[:name] = service[:name].downcase if service[:name]
213-
service_info[:resource] = service[:resource] if service[:resource]
214-
service_obj = host.services.find_or_create_by(service_info)
215-
if service_obj.id.nil?
216-
elog("Failed to create service #{service_info.inspect} for host #{host.name} (#{host.address})")
217-
return
218-
end
219-
service_obj.state ||= Msf::ServiceState::Open
220-
service_obj.info = service[:info] ? service[:info] : ''
221-
222-
if parents
223-
parents.each do |parent|
224-
service_obj.parents << parent if parent && !service_obj.parents.include?(parent)
225-
end
226-
end
227-
228-
service_obj
229-
end
230-
231-
end
232166
end

lib/msf/core/db_manager/session.rb

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -250,27 +250,18 @@ def infer_vuln_from_session(session, wspace)
250250
workspace: wspace,
251251
}
252252

253-
if session.exploit.respond_to?(:service_details) && session.exploit.service_details
254-
service_details = session.exploit.service_details
255-
service_name = service_details[:service_name]
256-
port = service_details[:port]
257-
if port.nil?
258-
port = session.respond_to?(:target_port) && session.target_port ? session.target_port : session.exploit_datastore["RPORT"]
259-
end
260-
proto = service_details[:protocol]
261-
vuln_info[:service] = host.services.find_or_create_by(name: service_name, port: port.to_i, proto: proto, state: 'open')
262-
end
263-
unless vuln_info[:service]
264-
port = session.respond_to?(:target_port) && session.target_port ? session.target_port : session.exploit_datastore["RPORT"]
265-
vuln_info[:service] = host.services.find_by_port(port.to_i) if port
266-
end
253+
port = session.exploit_datastore["RPORT"]
254+
service = (port ? host.services.find_by_port(port.to_i) : nil)
255+
256+
vuln_info[:service] = service if service
267257

268258
vuln = report_vuln(vuln_info)
269259

270260
attempt_info = {
271261
host: host,
272262
module: mod_fullname,
273263
refs: refs,
264+
service: service,
274265
session_id: s.id,
275266
timestamp: Time.now.utc,
276267
username: session.username,

lib/msf/core/db_manager/vuln.rb

Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,11 @@ def find_vuln_by_details(details_map, host, service=nil)
4444
other_vulns.empty? ? nil : other_vulns.first
4545
end
4646

47-
def find_vuln_by_refs(refs, host, service = nil, cve_only = true, resource = nil)
47+
def find_vuln_by_refs(refs, host, service = nil, cve_only = true)
4848
ref_ids = cve_only ? refs.find_all { |ref| ref.name.starts_with? 'CVE-'} : refs
4949
relation = host.vulns.joins(:refs)
5050
if !service.try(:id).nil?
51-
if resource
52-
return relation.where(service_id: service.try(:id), refs: { id: ref_ids}, resource: resource).first
53-
else
54-
return relation.where(service_id: service.try(:id), refs: { id: ref_ids}).first
55-
end
51+
return relation.where(service_id: service.try(:id), refs: { id: ref_ids}).first
5652
end
5753
return relation.where(refs: { id: ref_ids}).first
5854
end
@@ -84,20 +80,12 @@ def has_vuln?(name)
8480
# opts MUST contain
8581
# +:host+:: the host where this vulnerability resides
8682
# +:name+:: the friendly name for this vulnerability (title)
87-
# +:workspace+:: the workspace to report this vulnerability in
8883
#
8984
# opts can contain
9085
# +:info+:: a human readable description of the vuln, free-form text
9186
# +:refs+:: an array of Ref objects or string names of references
9287
# +:details+:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
9388
# +:sname+:: the name of the service this vulnerability relates to, used to associate it or create it.
94-
# +:exploited_at+:: a timestamp indicating when this vulnerability was exploited, if applicable
95-
# +:ref_ids+:: an array of reference IDs to associate with this vulnerability
96-
# +:service+:: a Mdm::Service object or a Hash with service attributes to associate this vulnerability with
97-
# +:port+:: the port number of the service this vulnerability relates to, if applicable
98-
# +:proto+:: the transport layer protocol of the service this vulnerability relates to, if applicable
99-
# +:details_match+:: a Mdm:VulnDetail with details related to this vulnerability
100-
# +:resource+:: a resource hash to associate with this vulnerability, such as a URI or pipe name
10189
#
10290
def report_vuln(opts)
10391
return if not active
@@ -153,16 +141,7 @@ def report_vuln(opts)
153141
vuln = nil
154142

155143
# Identify the associated service
156-
service_opt = opts.delete(:service)
157-
case service_opt
158-
when Mdm::Service
159-
service = service_opt
160-
when Hash
161-
service = report_service(service_opt.merge(workspace: wspace, host: host))
162-
else
163-
dlog("Skipping service since it is not a Hash or Mdm::Service: #{service.class}")
164-
service = nil
165-
end
144+
service = opts.delete(:service)
166145

167146
# Treat port zero as no service
168147
if service or opts[:port].to_i > 0
@@ -181,17 +160,9 @@ def report_vuln(opts)
181160
sname = opts[:proto]
182161
end
183162

184-
# If sname and proto are not provided, this will assign the first service
185-
# registered in the database for this host with the given port and proto.
186-
# This is likely to be the TCP service.
187-
sopts = {
188-
workspace: wspace,
189-
host: host,
190-
port: opts[:port].to_i,
191-
proto: proto
192-
}
193-
sopts[:name] = sname if sname.present?
194-
service = report_service(sopts)
163+
services = host.services.where(port: opts[:port].to_i, proto: proto)
164+
services = services.where(name: sname) if sname.present?
165+
service = services.first_or_create
195166
end
196167

197168
# Try to find an existing vulnerability with the same service & references
@@ -201,12 +172,8 @@ def report_vuln(opts)
201172
# prevent dupes of the same vuln found by both local patch and
202173
# service detection.
203174
if rids and rids.length > 0
204-
if opts[:resource]
205-
vuln = find_vuln_by_refs(rids, host, service, nil, opts[:resource])
206-
else
207-
vuln = find_vuln_by_refs(rids, host, service)
208-
end
209-
vuln.service = service if vuln && !vuln.service_id?
175+
vuln = find_vuln_by_refs(rids, host, service)
176+
vuln.service = service if vuln
210177
end
211178
else
212179
# Try to find an existing vulnerability with the same host & references
@@ -227,17 +194,9 @@ def report_vuln(opts)
227194
# No matches, so create a new vuln record
228195
unless vuln
229196
if service
230-
if opts[:resource]
231-
vuln = service.vulns.find_by(name: name, resource: opts[:resource])
232-
else
233-
vuln = service.vulns.find_by_name(name)
234-
end
197+
vuln = service.vulns.find_by_name(name)
235198
else
236-
if opts[:resource]
237-
vuln = host.vulns.find_by(name: name, resource: opts[:resource])
238-
else
239-
vuln = host.vulns.find_by_name(name)
240-
end
199+
vuln = host.vulns.find_by_name(name)
241200
end
242201

243202
unless vuln
@@ -249,7 +208,6 @@ def report_vuln(opts)
249208
}
250209

251210
vinf[:service_id] = service.id if service
252-
vinf[:resource] = opts[:resource] if opts[:resource]
253211
vuln = Mdm::Vuln.create(vinf)
254212

255213
begin

lib/msf/core/exploit.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ module CompatDefaults
4949
# https://docs.metasploit.com/docs/development/developing-modules/guides/how-to-write-a-check-method.html
5050
#
5151
##
52-
class CheckCode < Struct.new(:code, :message, :reason, :details, :vuln)
52+
class CheckCode < Struct.new(:code, :message, :reason, :details)
5353
# Do customization here because we need class constants and special
5454
# optional values and the block mode of Struct.new does not support that.
5555
#
@@ -77,8 +77,8 @@ def Appears(reason = nil, details: {})
7777
self.new('appears', reason, details: details)
7878
end
7979

80-
def Vulnerable(reason = nil, details: {}, vuln: {})
81-
self.new('vulnerable', reason, details: details, vuln: vuln)
80+
def Vulnerable(reason = nil, details: {})
81+
self.new('vulnerable', reason, details: details)
8282
end
8383

8484
def Unsupported(reason = nil, details: {})
@@ -100,7 +100,7 @@ def ===(other)
100100
other.is_a?(self.class) && self.code == other.code
101101
end
102102

103-
def initialize(code, reason, details: {}, vuln: {})
103+
def initialize(code, reason, details: {})
104104
msg = case code
105105
when 'unknown'; 'Cannot reliably check exploitability.'
106106
when 'safe'; 'The target is not exploitable.'
@@ -111,7 +111,7 @@ def initialize(code, reason, details: {}, vuln: {})
111111
else
112112
''
113113
end
114-
super(code, "#{msg} #{reason}".strip, reason, details, vuln)
114+
super(code, "#{msg} #{reason}".strip, reason, details)
115115
end
116116

117117
#

0 commit comments

Comments
 (0)