Module: Msf::DBManager::Host

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

Instance Method Summary collapse

Instance Method Details

#add_host_tag(opts) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/msf/core/db_manager/host.rb', line 71

def add_host_tag(opts)
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
  tag_name = opts[:tag_name] # This will be the string of the tag that we are using.

  host = find_host_by_address_or_id(opts, wspace)

  # If a host was found
  if host
    # Set host_id to the ID of the host entry in the database that was found.
    host_id = host[:id]

    # Then proceed to go ahead and find potential tags that might have been already
    # created that match the one we are trying to add.
    possible_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.id = ? and tags.name = ?", wspace.id, host_id, tag_name).order("tags.id DESC").limit(1)

    # If one exists, then use it, otherwise create a new Mdm::Tag, and update
    # the data in the database if the entry was found to need updating (aka the tag
    # hasn't already been applied).
    # @type [Mdm::Tag]
    tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
    tag.name = tag_name
    tag.hosts = [host]
    tag.save! if tag.changed?
    tag
  end
end

#del_host(wspace, address, comm = '') ⇒ Object

TODO: doesn't appear to have any callers. How is this used? Deletes a host and associated data matching this address/comm



4
5
6
7
8
9
10
# File 'lib/msf/core/db_manager/host.rb', line 4

def del_host(wspace, address, comm='')
::ApplicationRecord.connection_pool.with_connection {
  address, scope = address.split('%', 2)
  host = wspace.hosts.find_by_address_and_comm(address, comm)
  host.destroy if host
}
end

#delete_host(opts) ⇒ Array

Deletes Host entries based on the IDs passed in.

Parameters:

  • opts (:ids)
    Array

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

Returns:

  • (Array)

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

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/msf/core/db_manager/host.rb', line 16

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

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

    return deleted
  }
end

#delete_host_tag(opts) ⇒ Object

TODO:

This will have to be pulled out if tags are used for more than just hosts

ATM it will delete the tag from the tag table, not the host<->tag link



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/msf/core/db_manager/host.rb', line 100

def delete_host_tag(opts)
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
  tag_name = opts[:tag_name]
  tag_ids = []

  # If the command line included an address or address range then use this.
  # Otherwise delete all entries that match the given tag.
  host = find_host_by_address_or_id(opts, wspace)
  if host
    found_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.id = ? and tags.name = ?", wspace.id, host.id, tag_name)
    found_tags.each do |t|
      tag_ids << t.id
    end

    deleted_tags = []

    tag_ids.each do |id|
      tag = Mdm::Tag.find_by_id(id)
      deleted_tags << tag
      tag.destroy
    end

    return deleted_tags
  end
end

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

Iterates over the hosts table calling the supplied block with the host instance of each entry.



39
40
41
42
43
44
45
# File 'lib/msf/core/db_manager/host.rb', line 39

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

#find_host_by_address_or_id(opts, wspace) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/msf/core/db_manager/host.rb', line 55

def find_host_by_address_or_id(opts, wspace)
  # Find the host entry in the current workspace by searching on
  # the ID if available. If this isn't possible then try search on
  # the IP address of the host we are currently processing, then save the database
  # entry into the "host" variable.
  if opts[:id]
    host = wspace.hosts.find(opts[:id])
  elsif opts[:address]
    host = wspace.hosts.find_by_address(opts[:address])
  else
    raise ::ArgumentError, 'opts hash did not contain an :id or :address entry!'
  end

  host
end

#find_or_create_host(opts) ⇒ Object

Exactly like report_host but waits for the database to create a host and returns it.



48
49
50
51
52
53
# File 'lib/msf/core/db_manager/host.rb', line 48

def find_or_create_host(opts)
  host = get_host(opts.clone)
  return host unless host.nil?

  report_host(opts)
end

#get_host(opts) ⇒ Object

Find a host. Performs no database writes.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/msf/core/db_manager/host.rb', line 139

def get_host(opts)
  if opts.kind_of? ::Mdm::Host
    return opts
  elsif opts.kind_of? String
    raise RuntimeError, "This invocation of get_host is no longer supported: #{caller}"
  else
    address = opts[:addr] || opts[:address] || opts[:host] || return
    return address if address.kind_of? ::Mdm::Host
  end
::ApplicationRecord.connection_pool.with_connection {
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)

  address = Msf::Util::Host.normalize_host(address)
  return wspace.hosts.find_by_address(address)
}
end

#get_host_tags(opts) ⇒ Object



126
127
128
129
130
131
132
133
134
# File 'lib/msf/core/db_manager/host.rb', line 126

def get_host_tags(opts)
  wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
  host_id = opts[:id]

  host = wspace.hosts.find(host_id)
  if host
    host.tags
  end
end

#host_state_changed(host, ostate) ⇒ Object



181
182
183
184
185
186
187
188
# File 'lib/msf/core/db_manager/host.rb', line 181

def host_state_changed(host, ostate)
  begin
    framework.events.on_db_host_state(host, ostate)
  rescue ::Exception => e
    wlog("Exception in on_db_host_state event handler: #{e.class}: #{e}")
    wlog("Call Stack\n#{e.backtrace.join("\n")}")
  end
end

#hosts(opts) ⇒ Object

Returns a list of all hosts in the database



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/msf/core/db_manager/host.rb', line 157

def hosts(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::Host.find(opts[:id]))
    end

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

    conditions = {}
    conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if opts[:non_dead]
    conditions[:address] = opts[:address] if opts[:address] && !opts[:address].empty?

    if opts[:search_term] && !opts[:search_term].empty?
      column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Host, opts[:search_term])
      tag_conditions = Arel::Nodes::Regexp.new(Mdm::Tag.arel_table[:name], Arel::Nodes.build_quoted("(?mi)#{opts[:search_term]}"))
      search_conditions = column_search_conditions.or(tag_conditions)
      wspace.hosts.where(conditions).where(search_conditions).includes(:tags).references(:tags).order(:address)
    else
      wspace.hosts.where(conditions).order(:address)
    end
  }
end

#report_host(opts) ⇒ Object

Report a host's attributes such as operating system and service pack

The opts parameter MUST contain

:host

– the host's ip address

The opts parameter can contain:

:state

– one of the Msf::HostState constants

:os_name

– something like “Windows”, “Linux”, or “Mac OS X”

:os_flavor

– something like “Enterprise”, “Pro”, or “Home”

:os_sp

– something like “SP2”

:os_lang

– something like “English”, “French”, or “en-US”

:arch

– one of the ARCHITECTURES listed in metasploit_data_models/app/models/mdm/host.rb

:mac

– the host's MAC address

:scope

– interface identifier for link-local IPv6

:virtual_host

– the name of the virtualization software, eg “VMWare”, “QEMU”, “Xen”, “Docker”, etc.



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
289
290
291
292
293
294
295
296
# File 'lib/msf/core/db_manager/host.rb', line 207

def report_host(opts)

  return if !active
  addr = opts.delete(:host) || return

  # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  if addr.eql? "Remote Pipe"
    return
  end

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

  begin
    retry_attempts ||= 0
    if !addr.kind_of? ::Mdm::Host
      original_addr = addr
      addr = Msf::Util::Host.normalize_host(original_addr)

      unless ipv46_validator(addr)
        raise ::ArgumentError, "Invalid IP address in report_host(): #{original_addr}"
      end

      conditions = {address: addr}
      conditions[:comm] = opts[:comm] if !opts[:comm].nil? && opts[:comm].length > 0
      host = wspace.hosts.where(conditions).first_or_initialize
    else
      host = addr
    end

    ostate = host.state

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

    # Truncate the name field at the maximum field length
    if opts[:name]
      opts[:name] = opts[:name][0,255]
    end

    opts.each do |k,v|
      if host.attribute_names.include?(k.to_s)
        unless host.attribute_locked?(k.to_s)
          host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '')
        end
      elsif !v.blank?
        dlog("Unknown attribute for ::Mdm::Host: #{k}")
      end
    end
    host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info

    # Set default fields if needed
    host.state = Msf::HostState::Alive if host.state.nil? || host.state.empty?
    host.comm = '' unless host.comm
    host.workspace = wspace unless host.workspace

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

    host_state_changed(host, ostate) if host.state != ostate

    if host.changed?
      msf_import_timestamps(opts, host)
      host.save!
    end
  rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
    # two concurrent report requests for a new host could result in a RecordNotUnique or
    # RecordInvalid exception, simply retry the report once more as an optimistic approach
    retry if (retry_attempts+=1) <= 1
    raise
  end

  if opts[:task]
    Mdm::TaskHost.create(
        :task => opts[:task],
        :host => host
    )
  end

  host
}
end

#update_host(opts) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/msf/core/db_manager/host.rb', line 298

def update_host(opts)
  ::ApplicationRecord.connection_pool.with_connection {
    # process workspace string for update if included in opts
    wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
    opts = opts.clone()
    opts[:workspace] = wspace if wspace

    id = opts.delete(:id)
    host = Mdm::Host.find(id)
    host.update!(opts)
    return host
  }
end