2014-10-09 15:29:07 -05:00
module Msf::DBManager::Note
#
# This method iterates the notes table calling the supplied block with the
# note instance of each entry.
#
2018-03-22 21:00:06 -05:00
def each_note ( wspace = framework . db . workspace , & block )
2020-04-03 09:38:15 -05:00
:: ApplicationRecord . connection_pool . with_connection {
2014-10-09 15:29:07 -05:00
wspace . notes . each do | note |
block . call ( note )
end
}
end
#
# Find or create a note matching this type/data
#
def find_or_create_note ( opts )
report_note ( opts )
end
#
# This methods returns a list of all notes in the database
#
2018-03-27 16:36:56 -04:00
def notes ( opts )
2020-04-03 09:38:15 -05:00
:: ApplicationRecord . connection_pool . with_connection {
2018-07-18 15:18:22 -05:00
# If we have the ID, there is no point in creating a complex query.
2018-07-31 10:58:01 -05:00
if opts [ :id ] && ! opts [ :id ] . to_s . empty?
2018-07-18 15:18:22 -05:00
return Array . wrap ( Mdm :: Note . find ( opts [ :id ] ) )
end
2018-04-09 14:50:37 -05:00
wspace = Msf :: Util :: DBManager . process_opts_workspace ( opts , framework )
2019-11-25 11:58:27 -06:00
opts = opts . clone ( )
opts . delete ( :workspace )
2018-03-27 16:36:56 -04:00
2018-05-21 17:37:51 -04:00
data = opts . delete ( :data )
2018-03-27 16:36:56 -04:00
search_term = opts . delete ( :search_term )
2018-04-12 17:37:16 -04:00
results = wspace . notes . includes ( :host ) . where ( opts )
2018-05-21 17:37:51 -04:00
# Compare the deserialized data from the DB to the search data since the column is serialized.
unless data . nil?
results = results . select { | note | note . data == data }
end
2018-03-27 16:36:56 -04:00
if search_term && ! search_term . empty?
2018-04-12 17:37:16 -04:00
re_search_term = / #{ search_term } /mi
results = results . select { | note |
note . attribute_names . any? { | a | note [ a . intern ] . to_s . match ( re_search_term ) }
}
2018-03-27 16:36:56 -04:00
end
2018-04-12 17:37:16 -04:00
results
2018-03-27 16:36:56 -04:00
}
2014-10-09 15:29:07 -05:00
end
#
# Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service.
#
# opts MUST contain
# +:type+:: The type of note, e.g. smb_peer_os
#
# opts can contain
# +:workspace+:: the workspace to associate with this Note
# +:host+:: an IP address or a Host object to associate with this Note
# +:service+:: a Service object to associate with this Note
# +:data+:: whatever it is you're making a note of
# +:port+:: along with +:host+ and +:proto+, a service to associate with this Note
# +:proto+:: along with +:host+ and +:port+, a service to associate with this Note
# +:update+:: what to do in case a similar Note exists, see below
#
# The +:update+ option can have the following values:
# +:unique+:: allow only a single Note per +:host+/+:type+ pair
2023-09-24 17:42:00 -04:00
# +:unique_data+:: like +:unique+, but also compare +:data+
2014-10-09 15:29:07 -05:00
# +:insert+:: always insert a new Note even if one with identical values exists
#
# If the provided +:host+ is an IP address and does not exist in the
# database, it will be created. If +:workspace+, +:host+ and +:service+
# are all omitted, the new Note will be associated with the current
# workspace.
#
def report_note ( opts )
return if not active
2020-04-03 09:38:15 -05:00
:: ApplicationRecord . connection_pool . with_connection {
2018-03-22 21:00:06 -05:00
wspace = Msf :: Util :: DBManager . process_opts_workspace ( opts , framework )
2019-11-25 11:58:27 -06:00
opts = opts . clone ( )
opts . delete ( :workspace )
2014-10-09 15:29:07 -05:00
seen = opts . delete ( :seen ) || false
crit = opts . delete ( :critical ) || false
host = nil
addr = nil
# Report the host so it's there for the Proc to use below
if opts [ :host ]
if opts [ :host ] . kind_of? :: Mdm :: Host
host = opts [ :host ]
else
2017-09-28 16:59:44 -05:00
addr = Msf :: Util :: Host . normalize_host ( opts [ :host ] )
2014-10-09 15:29:07 -05:00
host = report_host ( { :workspace = > wspace , :host = > addr } )
end
# Do the same for a service if that's also included.
if ( opts [ :port ] )
proto = nil
sname = nil
2014-11-13 14:56:17 -06:00
proto_lower = opts [ :proto ] . to_s . downcase # Catch incorrect usages
case proto_lower
2014-10-09 15:29:07 -05:00
when 'tcp' , 'udp'
2014-11-13 14:56:17 -06:00
proto = proto_lower
2014-10-09 15:29:07 -05:00
sname = opts [ :sname ] if opts [ :sname ]
2018-07-31 11:35:08 -05:00
# XXX: These normalizations are lazy af
when 'http' , 'smb'
proto = 'tcp'
sname = proto_lower
2014-10-09 15:29:07 -05:00
when 'dns' , 'snmp' , 'dhcp'
proto = 'udp'
2018-07-31 11:35:08 -05:00
sname = proto_lower
2014-10-09 15:29:07 -05:00
else
proto = 'tcp'
2018-07-31 11:35:08 -05:00
sname = proto_lower
2014-10-09 15:29:07 -05:00
end
sopts = {
:workspace = > wspace ,
:host = > host ,
:port = > opts [ :port ] ,
:proto = > proto
}
sopts [ :name ] = sname if sname
report_service ( sopts )
end
end
# Update Modes can be :unique, :unique_data, :insert
mode = opts [ :update ] || :unique
ret = { }
if addr and not host
host = get_host ( :workspace = > wspace , :host = > addr )
end
2020-09-22 11:56:27 +01:00
if host and ( opts [ :port ] and proto )
2020-09-16 11:56:09 +01:00
# only one result can be returned, as the +port+ field restricts potential results to a single service
service = services ( :workspace = > wspace ,
2020-09-30 14:28:09 -04:00
:hosts = > { address : host . address } ,
2020-09-22 11:56:27 +01:00
:proto = > proto ,
2020-09-16 11:56:09 +01:00
:port = > opts [ :port ] ) . first
2014-10-09 15:29:07 -05:00
elsif opts [ :service ] and opts [ :service ] . kind_of? :: Mdm :: Service
service = opts [ :service ]
end
2018-04-16 14:14:46 -04:00
2014-10-09 15:29:07 -05:00
ntype = opts . delete ( :type ) || opts . delete ( :ntype ) || ( raise RuntimeError , " A note :type or :ntype is required " )
2025-05-21 10:45:08 +01:00
unless opts [ :data ] . is_a? ( Hash )
2025-05-28 12:29:16 +01:00
stack_trace = caller . map { | line | " [-] #{ line } " } . join ( " \n " )
message = " \n [-] [DEPRECATION] Using #{ __method__ } with a non-hash data value is deprecated, please raise a Github issue with this output. \n [-] Call stack: \n #{ stack_trace } "
warn ( message )
# Additionally write to ~/.msf4/logs/framework.log - as this gets attached to GitHub issues etc
2025-05-21 10:45:08 +01:00
elog ( message )
end
2014-10-09 15:29:07 -05:00
data = opts [ :data ]
note = nil
conditions = { :ntype = > ntype }
conditions [ :host_id ] = host [ :id ] if host
conditions [ :service_id ] = service [ :id ] if service
2015-02-26 15:48:04 -06:00
conditions [ :vuln_id ] = opts [ :vuln_id ]
2014-10-09 15:29:07 -05:00
2020-08-06 19:00:39 +02:00
case mode . to_sym
2014-10-09 15:29:07 -05:00
when :unique
note = wspace . notes . where ( conditions ) . first_or_initialize
note . data = data
when :unique_data
notes = wspace . notes . where ( conditions )
# Don't make a new Note with the same data as one that already
# exists for the given: type and (host or service)
notes . each do | n |
# Compare the deserialized data from the table to the raw
# data we're looking for. Because of the serialization we
# can't do this easily or reliably in SQL.
if n . data == data
note = n
break
end
end
if not note
# We didn't find one with the data we're looking for, make
# a new one.
note = wspace . notes . new ( conditions . merge ( :data = > data ) )
end
else
# Otherwise, assume :insert, which means always make a new one
note = wspace . notes . new
if host
note . host_id = host [ :id ]
end
if opts [ :service ] and opts [ :service ] . kind_of? :: Mdm :: Service
note . service_id = opts [ :service ] [ :id ]
end
note . seen = seen
note . critical = crit
note . ntype = ntype
note . data = data
end
2015-02-24 13:36:57 -06:00
if opts [ :vuln_id ]
note . vuln_id = opts [ :vuln_id ]
end
2024-05-29 11:25:31 +01:00
msf_assign_timestamps ( opts , note )
2014-10-09 15:29:07 -05:00
note . save!
ret [ :note ] = note
}
end
2018-03-27 16:36:56 -04:00
# Update the attributes of a note entry with the values in opts.
# The values in opts should match the attributes to update.
#
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Note] The updated Mdm::Note object.
def update_note ( opts )
2020-04-03 09:38:15 -05:00
:: ApplicationRecord . connection_pool . with_connection {
2018-04-09 14:50:37 -05:00
wspace = Msf :: Util :: DBManager . process_opts_workspace ( opts , framework , false )
2019-11-25 11:58:27 -06:00
opts = opts . clone ( )
opts . delete ( :workspace )
2018-04-09 14:50:37 -05:00
opts [ :workspace ] = wspace if wspace
2018-03-27 16:36:56 -04:00
id = opts . delete ( :id )
2019-01-07 16:24:13 -06:00
note = Mdm :: Note . find ( id )
note . update! ( opts )
2019-01-10 11:04:42 -06:00
return note
2018-03-27 16:36:56 -04:00
}
end
# Deletes note entries based on the IDs passed in.
#
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the note entries to delete.
# @return [Array] Array containing the Mdm::Note objects that were successfully deleted.
def delete_note ( opts )
raise ArgumentError . new ( " The following options are required: :ids " ) if opts [ :ids ] . nil?
2020-04-03 09:38:15 -05:00
:: ApplicationRecord . connection_pool . with_connection {
2018-03-27 16:36:56 -04:00
deleted = [ ]
opts [ :ids ] . each do | note_id |
note = Mdm :: Note . find ( note_id )
begin
deleted << note . destroy
rescue # refs suck
elog ( " Forcibly deleting #{ note } " )
deleted << note . delete
end
end
return deleted
}
end
2014-11-13 14:30:43 -06:00
end