Module: Msf::DBManager::Vuln

Included in:
Msf::DBManager
Defined in:
lib/msf/core/db_manager/vuln.rb

Instance Method Summary collapse

Instance Method Details

#delete_vuln(opts) ⇒ Array

Deletes Vuln entries based on the IDs passed in.

Parameters:

  • opts (:ids)
    Array

    Array containing Integers corresponding to the IDs of the Vuln entries to delete.

Returns:

  • (Array)

    Array containing the Mdm::Vuln objects that were successfully deleted.

Raises:

  • (ArgumentError)


335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/msf/core/db_manager/vuln.rb', line 335

def delete_vuln(opts)
  raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?

::ApplicationRecord.connection_pool.with_connection {
  deleted = []
  opts[:ids].each do |vuln_id|
    vuln = Mdm::Vuln.find(vuln_id)
    begin
      deleted << vuln.destroy
    rescue # refs suck
      elog("Forcibly deleting #{vuln}")
      deleted << vuln.delete
    end
  end

  return deleted
}
end

#each_vuln(wspace = framework.db.workspace, &block) ⇒ Object

This method iterates the vulns table calling the supplied block with the vuln instance of each entry.



6
7
8
9
10
11
12
# File 'lib/msf/core/db_manager/vuln.rb', line 6

def each_vuln(wspace=framework.db.workspace, &block)
::ApplicationRecord.connection_pool.with_connection {
  wspace.vulns.each do |vulns|
    block.call(vulns)
  end
}
end

#find_or_create_vuln(opts) ⇒ Object

Find or create a vuln matching this service/name



17
18
19
# File 'lib/msf/core/db_manager/vuln.rb', line 17

def find_or_create_vuln(opts)
  report_vuln(opts)
end

#find_vuln_by_details(details_map, host, service = nil) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/msf/core/db_manager/vuln.rb', line 21

def find_vuln_by_details(details_map, host, service=nil)

  # Create a modified version of the criteria in order to match against
  # the joined version of the fields

  crit = {}
  details_map.each_pair do |k,v|
    crit[ "vuln_details.#{k}" ] = v
  end

  vuln = nil

  if service
    other_vulns = service.vulns.includes(:vuln_details).where(crit).to_a
    vuln = other_vulns.empty? ? nil : other_vulns.first
  end

  # Return if we matched based on service
  return vuln if vuln

  # Prevent matches against other services
  crit["vulns.service_id"] = nil if service
  other_vulns = host.vulns.includes(:vuln_details).where(crit).to_a
  other_vulns.empty? ? nil : other_vulns.first
end

#find_vuln_by_refs(refs, host, service = nil, cve_only = true, resource = nil) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/msf/core/db_manager/vuln.rb', line 47

def find_vuln_by_refs(refs, host, service = nil, cve_only = true, resource = nil)
  ref_ids = cve_only ? refs.find_all { |ref| ref.name.starts_with? 'CVE-'} : refs
  relation = host.vulns.joins(:refs)
  if !service.try(:id).nil?
    if resource
      return relation.where(service_id: service.try(:id), refs: { id: ref_ids}, resource: resource).first
    else
      return relation.where(service_id: service.try(:id), refs: { id: ref_ids}).first
    end
  end
  return relation.where(refs: { id: ref_ids}).first
end

#get_vuln(wspace, host, service, name, data = '') ⇒ Object

Raises:

  • (RuntimeError)


60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/msf/core/db_manager/vuln.rb', line 60

def get_vuln(wspace, host, service, name, data='')
  raise RuntimeError, "Not workspace safe: #{caller.inspect}"
::ApplicationRecord.connection_pool.with_connection {
  vuln = nil
  if (service)
    vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first()
  else
    vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first()
  end

  return vuln
}
end

#has_vuln?(name) ⇒ Boolean

Find a vulnerability matching this name

Returns:

  • (Boolean)


77
78
79
80
81
# File 'lib/msf/core/db_manager/vuln.rb', line 77

def has_vuln?(name)
::ApplicationRecord.connection_pool.with_connection {
  Mdm::Vuln.find_by_name(name)
}
end

#report_vuln(opts) ⇒ Object

opts MUST contain

:host

the host where this vulnerability resides

:name

the friendly name for this vulnerability (title)

:workspace

the workspace to report this vulnerability in

opts can contain

:info

a human readable description of the vuln, free-form text

:refs

an array of Ref objects or string names of references

:details

a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields

:sname

the name of the service this vulnerability relates to, used to associate it or create it.

:exploited_at

a timestamp indicating when this vulnerability was exploited, if applicable

:ref_ids

an array of reference IDs to associate with this vulnerability

:service

a Mdm::Service object or a Hash with service attributes to associate this vulnerability with

:port

the port number of the service this vulnerability relates to, if applicable

:proto

the transport layer protocol of the service this vulnerability relates to, if applicable

:details_match

a Mdm:VulnDetail with details related to this vulnerability

:resource

a resource hash to associate with this vulnerability, such as a URI or pipe name

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/msf/core/db_manager/vuln.rb', line 102

def report_vuln(opts)
  return if not active
  raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
  name = opts[:name] || return
  info = opts[:info]

::ApplicationRecord.connection_pool.with_connection {
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
  opts = opts.clone()
  opts.delete(:workspace)
  exploited_at = opts[:exploited_at] || opts["exploited_at"]
  details = opts.delete(:details)
  rids = opts.delete(:ref_ids)

  if opts[:refs]
    rids ||= []
    opts[:refs].each do |r|
      if r.instance_of?(Mdm::Module::Ref)
        str = r.name
      elsif (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
        str = "#{r.ctx_id}-#{r.ctx_val}"
      elsif (r.is_a?(Hash) and r[:ctx_id] and r[:ctx_val])
        str = "#{r[:ctx_id]}-#{r[:ctx_val]}"
      elsif r.is_a?(String)
        str = r
      end
      rids << find_or_create_ref(:name => str) unless str.nil?
    end
  end

  host = nil
  addr = nil
  if opts[:host].kind_of? ::Mdm::Host
    host = opts[:host]
  else
    host = report_host({:workspace => wspace, :host => opts[:host]})
    addr = Msf::Util::Host.normalize_host(opts[:host])
  end

  ret = {}

  # Truncate the info field at the maximum field length
  if info
    info = info[0,65535]
  end

  # Truncate the name field at the maximum field length
  name = name[0,255]

  # Placeholder for the vuln object
  vuln = nil

  # Identify the associated service
  service_opt = opts.delete(:service)
  case service_opt
  when Mdm::Service
    service = service_opt
  when Hash
    service = report_service(service_opt.merge(workspace: wspace, host: host))
  else
    dlog("Skipping service since it is not a Hash or Mdm::Service: #{service.class}")
    service = nil
  end

  # Treat port zero as no service
  if service or opts[:port].to_i > 0

    if not service
      proto = nil
      case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
      when 'tcp','udp'
        proto = opts[:proto]
        sname = opts[:sname]
      when 'dns','snmp','dhcp'
        proto = 'udp'
        sname = opts[:proto]
      else
        proto = 'tcp'
        sname = opts[:proto]
      end

      # If sname and proto are not provided, this will assign the first service
      # registered in the database for this host with the given port and proto.
      # This is likely to be the TCP service.
      sopts = {
        workspace: wspace,
        host: host,
        port: opts[:port].to_i,
        proto: proto
      }
      sopts[:name] = sname if sname.present?
      service = report_service(sopts)
    end

    # Try to find an existing vulnerability with the same service & references
    # If there are multiple matches, choose the one with the most matches
    # If a match is found on a vulnerability with no associated service,
    # update that vulnerability with our service information. This helps
    # prevent dupes of the same vuln found by both local patch and
    # service detection.
    if rids and rids.length > 0
      if opts[:resource]
        vuln = find_vuln_by_refs(rids, host, service, nil, opts[:resource])
      else
        vuln = find_vuln_by_refs(rids, host, service)
      end
      vuln.service = service if vuln && !vuln.service_id?
    end
  else
    # Try to find an existing vulnerability with the same host & references
    # If there are multiple matches, choose the one with the most matches
    if rids and rids.length > 0
      vuln = find_vuln_by_refs(rids, host)
    end
  end

  # Try to match based on vuln_details records
  if not vuln and opts[:details_match]
    vuln = find_vuln_by_details(opts[:details_match], host, service)
    if vuln && service && vuln.service.nil?
      vuln.service = service
    end
  end

  # No matches, so create a new vuln record
  unless vuln
    if service
      if opts[:resource]
        vuln = service.vulns.find_by(name: name, resource: opts[:resource])
      else
        vuln = service.vulns.find_by_name(name)
      end
    else
      if opts[:resource]
        vuln = host.vulns.find_by(name: name, resource: opts[:resource])
      else
        vuln = host.vulns.find_by_name(name)
      end
    end

    unless vuln

      vinf = {
        :host_id => host.id,
        :name    => name,
        :info    => info
      }

      vinf[:service_id] = service.id if service
      vinf[:resource] = opts[:resource] if opts[:resource]
      vuln = Mdm::Vuln.create(vinf)

      begin
        framework.events.on_db_vuln(vuln) if vuln
      rescue ::Exception => e
        wlog("Exception in on_db_vuln event handler: #{e.class}: #{e}")
        wlog("Call Stack\n#{e.backtrace.join("\n")}")
      end

    end
  end

  # Set the exploited_at value if provided
  vuln.exploited_at = exploited_at if exploited_at

  # Vuln origin ignored, rationale:
  #   https://github.com/rapid7/metasploit-framework/pull/19817#issuecomment-2615656036
  # vuln.origin = opts[:origin] if opts[:origin]

  # Merge the references
  if rids
    vuln.refs << (rids - vuln.refs)
  end

  # Finalize
  if vuln.changed?
    msf_assign_timestamps(opts, vuln)
    vuln.save!
  end

  # Handle vuln_details parameters
  report_vuln_details(vuln, details) if details

  vuln
}
end

#update_vuln(opts) ⇒ Mdm::Vuln

Update the attributes of a Vuln entry with the values in opts. The values in opts should match the attributes to update.

Parameters:

  • opts (Hash)

    Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.

Returns:

  • (Mdm::Vuln)

    The updated Mdm::Vuln object.



319
320
321
322
323
324
325
326
327
328
329
# File 'lib/msf/core/db_manager/vuln.rb', line 319

def update_vuln(opts)
::ApplicationRecord.connection_pool.with_connection {
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
  opts = opts.clone()
  opts.delete(:workspace)
  opts[:workspace] = wspace if wspace
  v = Mdm::Vuln.find(opts.delete(:id))
  v.update!(opts)
  v
}
end

#vulns(opts) ⇒ Object

This methods returns a list of all vulnerabilities in the database



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/msf/core/db_manager/vuln.rb', line 293

def vulns(opts)
  ::ApplicationRecord.connection_pool.with_connection {
    # If we have the ID, there is no point in creating a complex query.
    if opts[:id] && !opts[:id].to_s.empty?
      return Array.wrap(Mdm::Vuln.find(opts[:id]))
    end

    wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
    opts = opts.clone()
    opts.delete(:workspace)

    search_term = opts.delete(:search_term)
    if search_term && !search_term.empty?
      column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Vuln, search_term)
      wspace.vulns.includes(:host).where(opts).where(column_search_conditions)
    else
      wspace.vulns.includes(:host).where(opts)
    end
  }
end