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


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

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

  host_id = opts[:id]
  tag_name = opts[:tag_name]

  host = wspace.hosts.find(host_id)
  if host
    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)
    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


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

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

  host_id = opts[:id]
  tag_name = opts[:tag_name]
  tag_ids = []

  host = wspace.hosts.find(host_id)
  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

    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_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.


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/msf/core/db_manager/host.rb', line 113

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


100
101
102
103
104
105
106
107
108
# File 'lib/msf/core/db_manager/host.rb', line 100

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

#has_host?(wspace, addr) ⇒ Boolean

Look for an address across all comms

Returns:

  • (Boolean)

131
132
133
134
135
136
# File 'lib/msf/core/db_manager/host.rb', line 131

def has_host?(wspace,addr)
::ApplicationRecord.connection_pool.with_connection {
  address, scope = addr.split('%', 2)
  wspace.hosts.find_by_address(addr)
}
end

#host_state_changed(host, ostate) ⇒ Object


163
164
165
166
167
168
169
170
# File 'lib/msf/core/db_manager/host.rb', line 163

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


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

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.


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
289
# File 'lib/msf/core/db_manager/host.rb', line 189

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
      addr = Msf::Util::Host.normalize_host(addr)

      unless ipv46_validator(addr)
        raise ::ArgumentError, "Invalid IP address in report_host(): #{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

    if opts[:os_name]
      os_name, os_flavor = split_windows_os_name(opts[:os_name])
      opts[:os_name] = os_name if os_name.present?
      if opts[:os_flavor].present?
        if os_flavor.present? # only prepend if there is a value that needs it
          opts[:os_flavor] = os_flavor + opts[:os_flavor]
        end
      else
        opts[:os_flavor] = os_flavor
      end
    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

#split_windows_os_name(os_name) ⇒ Object


305
306
307
308
309
310
# File 'lib/msf/core/db_manager/host.rb', line 305

def split_windows_os_name(os_name)
  return [] if os_name.nil?
  flavor_match = os_name.match(/Windows\s+(.*)/)
  return [] if flavor_match.nil?
  ["Windows", flavor_match.captures.first]
end

#update_host(opts) ⇒ Object


291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/msf/core/db_manager/host.rb', line 291

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