PageRenderTime 9ms CodeModel.GetById 81ms app.highlight 375ms RepoModel.GetById 17ms app.codeStats 3ms

/lib/msf/core/db.rb

https://bitbucket.org/technopunk2099/metasploit-framework
Ruby | 6413 lines | 5194 code | 481 blank | 738 comment | 493 complexity | b628b679e2f04ffd13c5ea362d7a9e0e MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1# -*- coding: binary -*-
   2# Check Rex::Parser.nokogiri_loaded for status of the Nokogiri parsers
   3require 'rex/parser/nmap_nokogiri'
   4require 'rex/parser/nexpose_simple_nokogiri'
   5require 'rex/parser/nexpose_raw_nokogiri'
   6require 'rex/parser/foundstone_nokogiri'
   7require 'rex/parser/mbsa_nokogiri'
   8require 'rex/parser/acunetix_nokogiri'
   9require 'rex/parser/appscan_nokogiri'
  10require 'rex/parser/burp_session_nokogiri'
  11require 'rex/parser/ci_nokogiri'
  12require 'rex/parser/wapiti_nokogiri'
  13require 'rex/parser/openvas_nokogiri'
  14require 'rex/parser/fusionvm_nokogiri'
  15
  16# Legacy XML parsers -- these will be converted some day
  17
  18require 'rex/parser/nmap_xml'
  19require 'rex/parser/nexpose_xml'
  20require 'rex/parser/retina_xml'
  21require 'rex/parser/netsparker_xml'
  22require 'rex/parser/nessus_xml'
  23require 'rex/parser/ip360_xml'
  24require 'rex/parser/ip360_aspl_xml'
  25
  26require 'rex/socket'
  27require 'zip'
  28require 'packetfu'
  29require 'uri'
  30require 'tmpdir'
  31require 'csv'
  32
  33
  34module Msf
  35
  36###
  37#
  38# The states that a host can be in.
  39#
  40###
  41module HostState
  42	#
  43	# The host is alive.
  44	#
  45	Alive   = "alive"
  46	#
  47	# The host is dead.
  48	#
  49	Dead    = "down"
  50	#
  51	# The host state is unknown.
  52	#
  53	Unknown = "unknown"
  54end
  55
  56###
  57#
  58# The states that a service can be in.
  59#
  60###
  61module ServiceState
  62	Open      = "open"
  63	Closed    = "closed"
  64	Filtered  = "filtered"
  65	Unknown   = "unknown"
  66end
  67
  68###
  69#
  70# Events that can occur in the host/service database.
  71#
  72###
  73module DatabaseEvent
  74
  75	#
  76	# Called when an existing host's state changes
  77	#
  78	def on_db_host_state(host, ostate)
  79	end
  80
  81	#
  82	# Called when an existing service's state changes
  83	#
  84	def on_db_service_state(host, port, ostate)
  85	end
  86
  87	#
  88	# Called when a new host is added to the database.  The host parameter is
  89	# of type Host.
  90	#
  91	def on_db_host(host)
  92	end
  93
  94	#
  95	# Called when a new client is added to the database.  The client
  96	# parameter is of type Client.
  97	#
  98	def on_db_client(client)
  99	end
 100
 101	#
 102	# Called when a new service is added to the database.  The service
 103	# parameter is of type Service.
 104	#
 105	def on_db_service(service)
 106	end
 107
 108	#
 109	# Called when an applicable vulnerability is found for a service.  The vuln
 110	# parameter is of type Vuln.
 111	#
 112	def on_db_vuln(vuln)
 113	end
 114
 115	#
 116	# Called when a new reference is created.
 117	#
 118	def on_db_ref(ref)
 119	end
 120
 121end
 122
 123class DBImportError < RuntimeError
 124end
 125
 126###
 127#
 128# The DB module ActiveRecord definitions for the DBManager
 129#
 130###
 131class DBManager
 132
 133	def rfc3330_reserved(ip)
 134		case ip.class.to_s
 135		when "PacketFu::Octets"
 136			ip_x = ip.to_x
 137			ip_i = ip.to_i
 138		when "String"
 139			if ipv46_validator(ip)
 140				ip_x = ip
 141				ip_i = Rex::Socket.addr_atoi(ip)
 142			else
 143				raise ArgumentError, "Invalid IP address: #{ip.inspect}"
 144			end
 145		when "Fixnum"
 146			if (0..2**32-1).include? ip
 147				ip_x = Rex::Socket.addr_itoa(ip)
 148				ip_i = ip
 149			else
 150				raise ArgumentError, "Invalid IP address: #{ip.inspect}"
 151			end
 152		else
 153			raise ArgumentError, "Invalid IP address: #{ip.inspect}"
 154		end
 155		return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x
 156		return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x
 157		return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x
 158		return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x
 159		return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x
 160		return false
 161	end
 162
 163	def ipv46_validator(addr)
 164		ipv4_validator(addr) or ipv6_validator(addr)
 165	end
 166
 167	def ipv4_validator(addr)
 168		return false unless addr.kind_of? String
 169		Rex::Socket.is_ipv4?(addr)
 170	end
 171
 172	def ipv6_validator(addr)
 173		Rex::Socket.is_ipv6?(addr)
 174	end
 175
 176	# Takes a space-delimited set of ips and ranges, and subjects
 177	# them to RangeWalker for validation. Returns true or false.
 178	def validate_ips(ips)
 179		ret = true
 180		begin
 181			ips.split(/\s+/).each {|ip|
 182				unless Rex::Socket::RangeWalker.new(ip).ranges
 183					ret = false
 184					break
 185				end
 186				}
 187		rescue
 188			ret = false
 189		end
 190		return ret
 191	end
 192
 193
 194	#
 195	# Determines if the database is functional
 196	#
 197	def check
 198	::ActiveRecord::Base.connection_pool.with_connection {
 199		res = ::Mdm::Host.find(:first)
 200	}
 201	end
 202
 203
 204	def default_workspace
 205	::ActiveRecord::Base.connection_pool.with_connection {
 206		::Mdm::Workspace.default
 207	}
 208	end
 209
 210	def find_workspace(name)
 211	::ActiveRecord::Base.connection_pool.with_connection {
 212		::Mdm::Workspace.find_by_name(name)
 213	}
 214	end
 215
 216	#
 217	# Creates a new workspace in the database
 218	#
 219	def add_workspace(name)
 220	::ActiveRecord::Base.connection_pool.with_connection {
 221		::Mdm::Workspace.find_or_create_by_name(name)
 222	}
 223	end
 224
 225	def workspaces
 226	::ActiveRecord::Base.connection_pool.with_connection {
 227		::Mdm::Workspace.find(:all)
 228	}
 229	end
 230
 231	#
 232	# Wait for all pending write to finish
 233	#
 234	def sync
 235		# There is no more queue.
 236	end
 237
 238	#
 239	# Find a host.  Performs no database writes.
 240	#
 241	def get_host(opts)
 242		if opts.kind_of? ::Mdm::Host
 243			return opts
 244		elsif opts.kind_of? String
 245			raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}"
 246		else
 247			address = opts[:addr] || opts[:address] || opts[:host] || return
 248			return address if address.kind_of? ::Mdm::Host
 249		end
 250	::ActiveRecord::Base.connection_pool.with_connection {
 251		wspace = opts.delete(:workspace) || workspace
 252		if wspace.kind_of? String
 253			wspace = find_workspace(wspace)
 254		end
 255
 256		address = normalize_host(address)
 257		return wspace.hosts.find_by_address(address)
 258	}
 259	end
 260
 261	#
 262	# Exactly like report_host but waits for the database to create a host and returns it.
 263	#
 264	def find_or_create_host(opts)
 265		report_host(opts)
 266	end
 267
 268	#
 269	# Report a host's attributes such as operating system and service pack
 270	#
 271	# The opts parameter MUST contain
 272	# +:host+::         -- the host's ip address
 273	#
 274	# The opts parameter can contain:
 275	# +:state+::        -- one of the Msf::HostState constants
 276	# +:os_name+::      -- one of the Msf::OperatingSystems constants
 277	# +:os_flavor+::    -- something like "XP" or "Gentoo"
 278	# +:os_sp+::        -- something like "SP2"
 279	# +:os_lang+::      -- something like "English", "French", or "en-US"
 280	# +:arch+::         -- one of the ARCH_* constants
 281	# +:mac+::          -- the host's MAC address
 282	# +:scope+::        -- interface identifier for link-local IPv6
 283	# +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc.
 284	#
 285	def report_host(opts)
 286
 287		return if not active
 288		addr = opts.delete(:host) || return
 289
 290		# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
 291		if addr.eql? "Remote Pipe"
 292			return
 293		end
 294
 295	::ActiveRecord::Base.connection_pool.with_connection {
 296		wspace = opts.delete(:workspace) || workspace
 297		if wspace.kind_of? String
 298			wspace = find_workspace(wspace)
 299		end
 300
 301		ret = { }
 302
 303		if not addr.kind_of? ::Mdm::Host
 304			addr = normalize_host(addr)
 305			addr, scope = addr.split('%', 2)
 306			opts[:scope] = scope if scope
 307
 308			unless ipv46_validator(addr)
 309				raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
 310			end
 311
 312			if opts[:comm] and opts[:comm].length > 0
 313				host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
 314			else
 315				host = wspace.hosts.find_or_initialize_by_address(addr)
 316			end
 317		else
 318			host = addr
 319		end
 320
 321		# Truncate the info field at the maximum field length
 322		if opts[:info]
 323			opts[:info] = opts[:info][0,65535]
 324		end
 325
 326		# Truncate the name field at the maximum field length
 327		if opts[:name]
 328			opts[:name] = opts[:name][0,255]
 329		end
 330
 331		opts.each { |k,v|
 332			if (host.attribute_names.include?(k.to_s))
 333				unless host.attribute_locked?(k.to_s)
 334					host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
 335				end
 336			else
 337				dlog("Unknown attribute for ::Mdm::Host: #{k}")
 338			end
 339		}
 340		host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info
 341
 342		# Set default fields if needed
 343		host.state       = HostState::Alive if not host.state
 344		host.comm        = ''        if not host.comm
 345		host.workspace   = wspace    if not host.workspace
 346
 347		if host.changed?
 348			msf_import_timestamps(opts,host)
 349			host.save!
 350		end
 351
 352		host
 353	}
 354	end
 355
 356
 357	#
 358	# Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
 359	#
 360	# The opts parameter MUST contain the following entries
 361	# +:host+::           -- the host's ip address
 362	# +:info+::           -- the information hash
 363	# * 'Computer'        -- the host name
 364	# * 'OS'              -- the operating system string
 365	# * 'Architecture'    -- the hardware architecture
 366	# * 'System Language' -- the system language
 367	#
 368	# The opts parameter can contain:
 369	# +:workspace+::      -- the workspace for this host
 370	#
 371	def update_host_via_sysinfo(opts)
 372
 373		return if not active
 374		addr = opts.delete(:host) || return
 375		info = opts.delete(:info) || return
 376
 377		# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
 378		if addr.eql? "Remote Pipe"
 379			return
 380		end
 381
 382	::ActiveRecord::Base.connection_pool.with_connection {
 383		wspace = opts.delete(:workspace) || workspace
 384		if wspace.kind_of? String
 385			wspace = find_workspace(wspace)
 386		end
 387
 388		if not addr.kind_of? ::Mdm::Host
 389			addr = normalize_host(addr)
 390			addr, scope = addr.split('%', 2)
 391			opts[:scope] = scope if scope
 392
 393			unless ipv46_validator(addr)
 394				raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
 395			end
 396
 397			if opts[:comm] and opts[:comm].length > 0
 398				host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
 399			else
 400				host = wspace.hosts.find_or_initialize_by_address(addr)
 401			end
 402		else
 403			host = addr
 404		end
 405
 406		res = {}
 407
 408		if info['Computer']
 409			res[:name] = info['Computer']
 410		end
 411
 412		if info['Architecture']
 413			res[:arch] = info['Architecture'].split(/\s+/).first
 414		end
 415
 416		if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
 417			res[:os_name]   = "Microsoft Windows"
 418			res[:os_flavor] = $1.strip
 419			build = $2.strip
 420
 421			if build =~ /Service Pack (\d+)/
 422				res[:os_sp] = "SP" + $1
 423			else
 424				res[:os_sp] = "SP0"
 425			end
 426		end
 427
 428		if info["System Language"]
 429			case info["System Language"]
 430				when /^en_/
 431					res[:os_lang] = "English"
 432			end
 433		end
 434
 435
 436		# Truncate the info field at the maximum field length
 437		if res[:info]
 438			res[:info] = res[:info][0,65535]
 439		end
 440
 441		# Truncate the name field at the maximum field length
 442		if res[:name]
 443			res[:name] = res[:name][0,255]
 444		end
 445
 446		res.each { |k,v|
 447
 448			if (host.attribute_names.include?(k.to_s))
 449				unless host.attribute_locked?(k.to_s)
 450					host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
 451				end
 452			else
 453				dlog("Unknown attribute for Host: #{k}")
 454			end
 455		}
 456
 457		# Set default fields if needed
 458		host.state       = HostState::Alive if not host.state
 459		host.comm        = ''        if not host.comm
 460		host.workspace   = wspace    if not host.workspace
 461
 462		if host.changed?
 463			host.save!
 464		end
 465
 466		host
 467	}
 468	end
 469	#
 470	# Iterates over the hosts table calling the supplied block with the host
 471	# instance of each entry.
 472	#
 473	def each_host(wspace=workspace, &block)
 474	::ActiveRecord::Base.connection_pool.with_connection {
 475		wspace.hosts.each do |host|
 476			block.call(host)
 477		end
 478	}
 479	end
 480
 481	#
 482	# Returns a list of all hosts in the database
 483	#
 484	def hosts(wspace = workspace, only_up = false, addresses = nil)
 485	::ActiveRecord::Base.connection_pool.with_connection {
 486		conditions = {}
 487		conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
 488		conditions[:address] = addresses if addresses
 489		wspace.hosts.where(conditions).order(:address)
 490	}
 491	end
 492
 493
 494
 495	def find_or_create_service(opts)
 496		report_service(opts)
 497	end
 498
 499	#
 500	# Record a service in the database.
 501	#
 502	# opts MUST contain
 503	# +:host+::  the host where this service is running
 504	# +:port+::  the port where this service listens
 505	# +:proto+:: the transport layer protocol (e.g. tcp, udp)
 506	#
 507	# opts may contain
 508	# +:name+::  the application layer protocol (e.g. ssh, mssql, smb)
 509	# +:sname+:: an alias for the above
 510	#
 511	def report_service(opts)
 512		return if not active
 513	::ActiveRecord::Base.connection_pool.with_connection { |conn|
 514		addr  = opts.delete(:host) || return
 515		hname = opts.delete(:host_name)
 516		hmac  = opts.delete(:mac)
 517		host  = nil
 518		wspace = opts.delete(:workspace) || workspace
 519		hopts = {:workspace => wspace, :host => addr}
 520		hopts[:name] = hname if hname
 521		hopts[:mac]  = hmac  if hmac
 522
 523		# Other report_* methods take :sname to mean the service name, so we
 524		# map it here to ensure it ends up in the right place despite not being
 525		# a real column.
 526		if opts[:sname]
 527			opts[:name] = opts.delete(:sname)
 528		end
 529
 530		if addr.kind_of? ::Mdm::Host
 531			host = addr
 532			addr = host.address
 533		else
 534			host = report_host(hopts)
 535		end
 536
 537		if opts[:port].to_i.zero?
 538			dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address])
 539			return nil
 540		end
 541
 542		ret  = {}
 543=begin
 544		host = get_host(:workspace => wspace, :address => addr)
 545		if host
 546			host.updated_at = host.created_at
 547			host.state      = HostState::Alive
 548			host.save!
 549		end
 550=end
 551
 552		proto = opts[:proto] || 'tcp'
 553
 554		service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto)
 555		opts.each { |k,v|
 556			if (service.attribute_names.include?(k.to_s))
 557				service[k] = ((v and k == :name) ? v.to_s.downcase : v)
 558			else
 559				dlog("Unknown attribute for Service: #{k}")
 560			end
 561		}
 562		service.state ||= ServiceState::Open
 563		service.info  ||= ""
 564
 565		if (service and service.changed?)
 566			msf_import_timestamps(opts,service)
 567			service.save!
 568		end
 569		ret[:service] = service
 570	}
 571	end
 572
 573	def get_service(wspace, host, proto, port)
 574	::ActiveRecord::Base.connection_pool.with_connection {
 575		host = get_host(:workspace => wspace, :address => host)
 576		return if not host
 577		return host.services.find_by_proto_and_port(proto, port)
 578	}
 579	end
 580
 581	#
 582	# Iterates over the services table calling the supplied block with the
 583	# service instance of each entry.
 584	#
 585	def each_service(wspace=workspace, &block)
 586	::ActiveRecord::Base.connection_pool.with_connection {
 587		services(wspace).each do |service|
 588			block.call(service)
 589		end
 590	}
 591	end
 592
 593	#
 594	# Returns a list of all services in the database
 595	#
 596	def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
 597	::ActiveRecord::Base.connection_pool.with_connection {
 598		conditions = {}
 599		conditions[:state] = [ServiceState::Open] if only_up
 600		conditions[:proto] = proto if proto
 601		conditions["hosts.address"] = addresses if addresses
 602		conditions[:port] = ports if ports
 603		conditions[:name] = names if names
 604		wspace.services.includes(:host).where(conditions).order("hosts.address, port")
 605	}
 606	end
 607
 608	# Returns a session based on opened_time, host address, and workspace
 609	# (or returns nil)
 610	def get_session(opts)
 611		return if not active
 612	::ActiveRecord::Base.connection_pool.with_connection {
 613		wspace = opts[:workspace] || opts[:wspace] || workspace
 614		addr   = opts[:addr] || opts[:address] || opts[:host] || return
 615		host = get_host(:workspace => wspace, :host => addr)
 616		time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
 617		::Mdm::Session.find_by_host_id_and_opened_at(host.id, time)
 618	}
 619	end
 620
 621	# Record a new session in the database
 622	#
 623	# opts MUST contain either
 624	# +:session+:: the Msf::Session object we are reporting
 625	# +:host+::    the Host object we are reporting a session on.
 626	#
 627	def report_session(opts)
 628		return if not active
 629	::ActiveRecord::Base.connection_pool.with_connection {
 630		if opts[:session]
 631			raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
 632			session = opts[:session]
 633			wspace = opts[:workspace] || find_workspace(session.workspace)
 634			h_opts = { }
 635			h_opts[:host]      = normalize_host(session)
 636			h_opts[:arch]      = session.arch if session.respond_to?(:arch) and session.arch
 637			h_opts[:workspace] = wspace
 638			host = find_or_create_host(h_opts)
 639			sess_data = {
 640				:host_id => host.id,
 641				:stype => session.type,
 642				:desc => session.info,
 643				:platform => session.platform,
 644				:via_payload => session.via_payload,
 645				:via_exploit => session.via_exploit,
 646				:routes => [],
 647				:datastore => session.exploit_datastore.to_h,
 648				:opened_at => Time.now.utc,
 649				:last_seen => Time.now.utc,
 650				:local_id => session.sid
 651			}
 652		elsif opts[:host]
 653			raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host
 654			host = opts[:host]
 655			sess_data = {
 656				:host_id => host.id,
 657				:stype => opts[:stype],
 658				:desc => opts[:desc],
 659				:platform => opts[:platform],
 660				:via_payload => opts[:via_payload],
 661				:via_exploit => opts[:via_exploit],
 662				:routes => opts[:routes] || [],
 663				:datastore => opts[:datastore],
 664				:opened_at => opts[:opened_at],
 665				:closed_at => opts[:closed_at],
 666				:last_seen => opts[:last_seen] || opts[:closed_at],
 667				:close_reason => opts[:close_reason],
 668			}
 669		else
 670			raise ArgumentError.new("Missing option :session or :host")
 671		end
 672		ret = {}
 673
 674		# Truncate the session data if necessary
 675		if sess_data[:desc]
 676			sess_data[:desc] = sess_data[:desc][0,255]
 677		end
 678
 679		# In the case of multi handler we cannot yet determine the true
 680		# exploit responsible. But we can at least show the parent versus
 681		# just the generic handler:
 682		if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
 683			sess_data[:via_exploit] = sess_data[:datastore]['ParentModule']
 684		end
 685
 686		s = ::Mdm::Session.new(sess_data)
 687		s.save!
 688
 689		if opts[:session]
 690			session.db_record = s
 691		end
 692
 693		# If this is a live session, we know the host is vulnerable to something.
 694		if opts[:session] and session.via_exploit
 695			return unless host
 696
 697			mod = framework.modules.create(session.via_exploit)
 698
 699			if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
 700				mod_fullname = sess_data[:datastore]['ParentModule']
 701				mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name
 702			else 
 703				mod_name = mod.name
 704				mod_fullname = mod.fullname
 705			end
 706
 707			vuln_info = {
 708				:host => host.address,
 709				:name => mod_name,
 710				:refs => mod.references,
 711				:workspace => wspace,
 712				:exploited_at => Time.now.utc,
 713				:info => "Exploited by #{mod_fullname} to create Session #{s.id}"
 714			}
 715
 716			port    = session.exploit_datastore["RPORT"]
 717			service = (port ? host.services.find_by_port(port.to_i) : nil)
 718
 719			vuln_info[:service] = service if service
 720
 721			vuln = framework.db.report_vuln(vuln_info)
 722			
 723			if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
 724				via_exploit = sess_data[:datastore]['ParentModule']
 725			else
 726				via_exploit = session.via_exploit
 727			end
 728			attempt_info = {
 729				:timestamp   => Time.now.utc,
 730				:workspace   => wspace,
 731				:module      => via_exploit,
 732				:username    => session.username,
 733				:refs        => mod.references,
 734				:session_id  => s.id,
 735				:host        => host,
 736				:service     => service,
 737				:vuln        => vuln
 738			}
 739
 740			framework.db.report_exploit_success(attempt_info)
 741			
 742		end
 743
 744		s
 745	}
 746	end
 747
 748	#
 749	# Record a session event in the database
 750	#
 751	# opts MUST contain one of:
 752	# +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting
 753	# +:etype+::   event type, enum: command, output, upload, download, filedelete
 754	#
 755	# opts may contain
 756	# +:output+::      the data for an output event
 757	# +:command+::     the data for an command event
 758	# +:remote_path+:: path to the associated file for upload, download, and filedelete events
 759	# +:local_path+::  path to the associated file for upload, and download
 760	#
 761	def report_session_event(opts)
 762		return if not active
 763		raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
 764		raise ArgumentError.new("Expected an :etype") unless opts[:etype]
 765		session = nil
 766
 767	::ActiveRecord::Base.connection_pool.with_connection {
 768		if opts[:session].respond_to? :db_record
 769			session = opts[:session].db_record
 770			if session.nil?
 771				# The session doesn't have a db_record which means
 772				#  a) the database wasn't connected at session registration time
 773				# or
 774				#  b) something awful happened and the report_session call failed
 775				#
 776				# Either way, we can't do anything with this session as is, so
 777				# log a warning and punt.
 778				wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})")
 779				return
 780			end
 781			event_data = { :created_at => Time.now }
 782		else
 783			session = opts[:session]
 784			event_data = { :created_at => opts[:created_at] }
 785		end
 786
 787		event_data[:session_id] = session.id
 788		[:remote_path, :local_path, :output, :command, :etype].each do |attr|
 789			event_data[attr] = opts[attr] if opts[attr]
 790		end
 791
 792		s = ::Mdm::SessionEvent.create(event_data)
 793	}
 794	end
 795
 796	def report_session_route(session, route)
 797		return if not active
 798		if session.respond_to? :db_record
 799			s = session.db_record
 800		else
 801			s = session
 802		end
 803		unless s.respond_to?(:routes)
 804			raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
 805		end
 806
 807	::ActiveRecord::Base.connection_pool.with_connection {
 808
 809		subnet, netmask = route.split("/")
 810		s.routes.create(:subnet => subnet, :netmask => netmask)
 811	}
 812	end
 813
 814	def report_session_route_remove(session, route)
 815		return if not active
 816		if session.respond_to? :db_record
 817			s = session.db_record
 818		else
 819			s = session
 820		end
 821		unless s.respond_to?(:routes)
 822			raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
 823		end
 824
 825	::ActiveRecord::Base.connection_pool.with_connection {
 826		subnet, netmask = route.split("/")
 827		r = s.routes.find_by_subnet_and_netmask(subnet, netmask)
 828		r.destroy if r
 829	}
 830	end
 831
 832
 833	def report_exploit_success(opts)
 834	::ActiveRecord::Base.connection_pool.with_connection {
 835
 836		wspace = opts.delete(:workspace) || workspace
 837		mrefs  = opts.delete(:refs) || return
 838		host   = opts.delete(:host)
 839		port   = opts.delete(:port)
 840		prot   = opts.delete(:proto)
 841		svc    = opts.delete(:service)
 842		vuln   = opts.delete(:vuln)
 843
 844		timestamp = opts.delete(:timestamp)
 845		username  = opts.delete(:username)
 846		mname     = opts.delete(:module)
 847
 848		# Look up or generate the host as appropriate
 849		if not (host and host.kind_of? ::Mdm::Host)
 850			if svc.kind_of? ::Mdm::Service
 851				host = svc.host
 852			else
 853				host = report_host(:workspace => wspace, :address => host )
 854			end
 855		end
 856
 857		# Bail if we dont have a host object
 858		return if not host
 859
 860		# Look up or generate the service as appropriate
 861		if port and svc.nil?
 862			svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port
 863		end
 864
 865		if not vuln
 866			# Create a references map from the module list
 867			ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
 868				if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
 869					"#{ref.ctx_id}-#{ref.ctx_val}"
 870				else
 871					ref.to_s
 872				end
 873			})
 874		
 875			# Try find a matching vulnerability
 876			vuln = find_vuln_by_refs(ref_objs, host, svc)
 877		end
 878
 879		# We have match, lets create a vuln_attempt record
 880		if vuln
 881			attempt_info = {
 882				:vuln_id      => vuln.id,
 883				:attempted_at => timestamp || Time.now.utc,
 884				:exploited    => true,
 885				:username     => username  || "unknown",
 886				:module       => mname
 887			}
 888
 889			attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
 890			attempt_info[:loot_id]    = opts[:loot_id]    if opts[:loot_id]
 891
 892			vuln.vuln_attempts.create(attempt_info)
 893	
 894			# Correct the vuln's associated service if necessary
 895			if svc and vuln.service_id.nil?
 896				vuln.service = svc
 897				vuln.save
 898			end
 899		end
 900
 901		# Report an exploit attempt all the same
 902		attempt_info = {
 903			:attempted_at => timestamp || Time.now.utc,
 904			:exploited    => true,
 905			:username     => username  || "unknown",
 906			:module       => mname
 907		}
 908
 909		attempt_info[:vuln_id]    = vuln.id           if vuln
 910		attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
 911		attempt_info[:loot_id]    = opts[:loot_id]    if opts[:loot_id]
 912		
 913		if svc
 914			attempt_info[:port]  = svc.port
 915			attempt_info[:proto] = svc.proto
 916		end
 917		
 918		if port and svc.nil?
 919			attempt_info[:port]  = port
 920			attempt_info[:proto] = prot || "tcp"
 921		end
 922
 923		host.exploit_attempts.create(attempt_info)
 924	}
 925	end
 926
 927	def report_exploit_failure(opts)
 928
 929	::ActiveRecord::Base.connection_pool.with_connection {
 930		wspace = opts.delete(:workspace) || workspace
 931		mrefs  = opts.delete(:refs) || return
 932		host   = opts.delete(:host)
 933		port   = opts.delete(:port)
 934		prot   = opts.delete(:proto)
 935		svc    = opts.delete(:service)
 936		vuln   = opts.delete(:vuln)
 937
 938		timestamp  = opts.delete(:timestamp)
 939		freason    = opts.delete(:fail_reason)
 940		fdetail    = opts.delete(:fail_detail)		
 941		username   = opts.delete(:username)
 942		mname      = opts.delete(:module)
 943
 944		# Look up the host as appropriate
 945		if not (host and host.kind_of? ::Mdm::Host)
 946			if svc.kind_of? ::Mdm::Service
 947				host = svc.host
 948			else
 949				host = get_host( :workspace => wspace, :address => host )
 950			end
 951		end
 952
 953		# Bail if we dont have a host object
 954		return if not host
 955
 956		# Look up the service as appropriate
 957		if port and svc.nil?
 958			prot ||= "tcp"
 959			svc = get_service(wspace, host, prot, port) if port
 960		end
 961
 962		if not vuln
 963			# Create a references map from the module list
 964			ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
 965				if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
 966					"#{ref.ctx_id}-#{ref.ctx_val}"
 967				else
 968					ref.to_s
 969				end
 970			})
 971		
 972			# Try find a matching vulnerability
 973			vuln = find_vuln_by_refs(ref_objs, host, svc)
 974		end
 975
 976		# Report a vuln_attempt if we found a match
 977		if vuln
 978			attempt_info = {
 979				:attempted_at => timestamp || Time.now.utc,
 980				:exploited    => false,
 981				:fail_reason  => freason,
 982				:fail_detail  => fdetail,
 983				:username     => username  || "unknown",
 984				:module       => mname
 985			}
 986
 987			vuln.vuln_attempts.create(attempt_info)
 988		end
 989
 990		# Report an exploit attempt all the same
 991		attempt_info = {
 992			:attempted_at => timestamp || Time.now.utc,
 993			:exploited    => false,
 994			:username     => username  || "unknown",
 995			:module       => mname,
 996			:fail_reason  => freason,
 997			:fail_detail  => fdetail
 998		}
 999
1000		attempt_info[:vuln_id] = vuln.id if vuln
1001
1002		if svc
1003			attempt_info[:port]  = svc.port
1004			attempt_info[:proto] = svc.proto
1005		end
1006		
1007		if port and svc.nil?
1008			attempt_info[:port]  = port
1009			attempt_info[:proto] = prot || "tcp"
1010		end
1011
1012		host.exploit_attempts.create(attempt_info)
1013	}
1014	end
1015
1016
1017	def report_vuln_attempt(vuln, opts)
1018	::ActiveRecord::Base.connection_pool.with_connection {
1019		return if not vuln
1020		info = {}
1021		
1022		# Opts can be keyed by strings or symbols
1023		::Mdm::VulnAttempt.column_names.each do |kn|
1024			k = kn.to_sym
1025			next if ['id', 'vuln_id'].include?(kn)
1026			info[k] = opts[kn] if opts[kn]
1027			info[k] = opts[k]  if opts[k]
1028		end
1029
1030		return unless info[:attempted_at]
1031
1032		vuln.vuln_attempts.create(info)
1033	}
1034	end
1035
1036	def report_exploit_attempt(host, opts)
1037	::ActiveRecord::Base.connection_pool.with_connection {
1038		return if not host
1039		info = {}
1040		
1041		# Opts can be keyed by strings or symbols
1042		::Mdm::VulnAttempt.column_names.each do |kn|
1043			k = kn.to_sym
1044			next if ['id', 'host_id'].include?(kn)
1045			info[k] = opts[kn] if opts[kn]
1046			info[k] = opts[k]  if opts[k]
1047		end
1048
1049		host.exploit_attempts.create(info)
1050	}
1051	end
1052
1053	def get_client(opts)
1054	::ActiveRecord::Base.connection_pool.with_connection {
1055		wspace = opts.delete(:workspace) || workspace
1056		host   = get_host(:workspace => wspace, :host => opts[:host]) || return
1057		client = host.clients.where({:ua_string => opts[:ua_string]}).first()
1058		return client
1059	}
1060	end
1061
1062	def find_or_create_client(opts)
1063		report_client(opts)
1064	end
1065
1066	#
1067	# Report a client running on a host.
1068	#
1069	# opts MUST contain
1070	# +:ua_string+::  the value of the User-Agent header
1071	# +:host+::       the host where this client connected from, can be an ip address or a Host object
1072	#
1073	# opts can contain
1074	# +:ua_name+::    one of the Msf::HttpClients constants
1075	# +:ua_ver+::     detected version of the given client
1076	# +:campaign+::   an id or Campaign object
1077	#
1078	# Returns a Client.
1079	#
1080	def report_client(opts)
1081		return if not active
1082	::ActiveRecord::Base.connection_pool.with_connection {
1083		addr = opts.delete(:host) || return
1084		wspace = opts.delete(:workspace) || workspace
1085		report_host(:workspace => wspace, :host => addr)
1086
1087		ret = {}
1088
1089		host = get_host(:workspace => wspace, :host => addr)
1090		client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string])
1091
1092		opts[:ua_string] = opts[:ua_string].to_s
1093
1094		campaign = opts.delete(:campaign)
1095		if campaign
1096			case campaign
1097			when Campaign
1098				opts[:campaign_id] = campaign.id
1099			else
1100				opts[:campaign_id] = campaign
1101			end
1102		end
1103
1104		opts.each { |k,v|
1105			if (client.attribute_names.include?(k.to_s))
1106				client[k] = v
1107			else
1108				dlog("Unknown attribute for Client: #{k}")
1109			end
1110		}
1111		if (client and client.changed?)
1112			client.save!
1113		end
1114		ret[:client] = client
1115	}
1116	end
1117
1118	#
1119	# This method iterates the vulns table calling the supplied block with the
1120	# vuln instance of each entry.
1121	#
1122	def each_vuln(wspace=workspace,&block)
1123	::ActiveRecord::Base.connection_pool.with_connection {
1124		wspace.vulns.each do |vulns|
1125			block.call(vulns)
1126		end
1127	}
1128	end
1129
1130	#
1131	# This methods returns a list of all vulnerabilities in the database
1132	#
1133	def vulns(wspace=workspace)
1134	::ActiveRecord::Base.connection_pool.with_connection {
1135		wspace.vulns
1136	}
1137	end
1138
1139	#
1140	# This methods returns a list of all credentials in the database
1141	#
1142	def creds(wspace=workspace)
1143	::ActiveRecord::Base.connection_pool.with_connection {
1144		Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id)
1145	}
1146	end
1147
1148	#
1149	# This method returns a list of all exploited hosts in the database.
1150	#
1151	def exploited_hosts(wspace=workspace)
1152	::ActiveRecord::Base.connection_pool.with_connection {
1153		wspace.exploited_hosts
1154	}
1155	end
1156
1157	#
1158	# This method iterates the notes table calling the supplied block with the
1159	# note instance of each entry.
1160	#
1161	def each_note(wspace=workspace, &block)
1162	::ActiveRecord::Base.connection_pool.with_connection {
1163		wspace.notes.each do |note|
1164			block.call(note)
1165		end
1166	}
1167	end
1168
1169	#
1170	# Find or create a note matching this type/data
1171	#
1172	def find_or_create_note(opts)
1173		report_note(opts)
1174	end
1175
1176	#
1177	# Report a Note to the database.  Notes can be tied to a ::Mdm::Workspace, Host, or Service.
1178	#
1179	# opts MUST contain
1180	# +:data+::  whatever it is you're making a note of
1181	# +:type+::  The type of note, e.g. smb_peer_os
1182	#
1183	# opts can contain
1184	# +:workspace+::  the workspace to associate with this Note
1185	# +:host+::       an IP address or a Host object to associate with this Note
1186	# +:service+::    a Service object to associate with this Note
1187	# +:port+::       along with +:host+ and +:proto+, a service to associate with this Note
1188	# +:proto+::      along with +:host+ and +:port+, a service to associate with this Note
1189	# +:update+::     what to do in case a similar Note exists, see below
1190	#
1191	# The +:update+ option can have the following values:
1192	# +:unique+::       allow only a single Note per +:host+/+:type+ pair
1193	# +:unique_data+::  like +:uniqe+, but also compare +:data+
1194	# +:insert+::       always insert a new Note even if one with identical values exists
1195	#
1196	# If the provided +:host+ is an IP address and does not exist in the
1197	# database, it will be created.  If +:workspace+, +:host+ and +:service+
1198	# are all omitted, the new Note will be associated with the current
1199	# workspace.
1200	#
1201	def report_note(opts)
1202		return if not active
1203	::ActiveRecord::Base.connection_pool.with_connection {
1204		wspace = opts.delete(:workspace) || workspace
1205		if wspace.kind_of? String
1206			wspace = find_workspace(wspace)
1207		end
1208		seen = opts.delete(:seen) || false
1209		crit = opts.delete(:critical) || false
1210		host = nil
1211		addr = nil
1212		# Report the host so it's there for the Proc to use below
1213		if opts[:host]
1214			if opts[:host].kind_of? ::Mdm::Host
1215				host = opts[:host]
1216			else
1217				addr = normalize_host(opts[:host])
1218				host = report_host({:workspace => wspace, :host => addr})
1219			end
1220			# Do the same for a service if that's also included.
1221			if (opts[:port])
1222				proto = nil
1223				sname = nil
1224				case opts[:proto].to_s.downcase # Catch incorrect usages
1225				when 'tcp','udp'
1226					proto = opts[:proto]
1227					sname = opts[:sname] if opts[:sname]
1228				when 'dns','snmp','dhcp'
1229					proto = 'udp'
1230					sname = opts[:proto]
1231				else
1232					proto = 'tcp'
1233					sname = opts[:proto]
1234				end
1235				sopts = {
1236					:workspace => wspace,
1237					:host  => host,
1238					:port  => opts[:port],
1239					:proto => proto
1240				}
1241				sopts[:name] = sname if sname
1242				report_service(sopts)
1243			end
1244		end
1245		# Update Modes can be :unique, :unique_data, :insert
1246		mode = opts[:update] || :unique
1247
1248		ret = {}
1249
1250		if addr and not host
1251			host = get_host(:workspace => wspace, :host => addr)
1252		end
1253		if host and (opts[:port] and opts[:proto])
1254			service = get_service(wspace, host, opts[:proto], opts[:port])
1255		elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service
1256			service = opts[:service]
1257		end
1258=begin
1259		if host
1260			host.updated_at = host.created_at
1261			host.state      = HostState::Alive
1262			host.save!
1263		end
1264=end
1265		ntype  = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
1266		data   = opts[:data] || (raise RuntimeError, "Note :data is required")
1267		method = nil
1268		args   = []
1269		note   = nil
1270
1271		conditions = { :ntype => ntype }
1272		conditions[:host_id] = host[:id] if host
1273		conditions[:service_id] = service[:id] if service
1274
1275		case mode
1276		when :unique
1277			notes = wspace.notes.where(conditions)
1278
1279			# Only one note of this type should exist, make a new one if it
1280			# isn't there. If it is, grab it and overwrite its data.
1281			if notes.empty?
1282				note = wspace.notes.new(conditions)
1283			else
1284				note = notes[0]
1285			end
1286			note.data = data
1287		when :unique_data
1288			notes = wspace.notes.where(conditions)
1289
1290			# Don't make a new Note with the same data as one that already
1291			# exists for the given: type and (host or service)
1292			notes.each do |n|
1293				# Compare the deserialized data from the table to the raw
1294				# data we're looking for.  Because of the serialization we
1295				# can't do this easily or reliably in SQL.
1296				if n.data == data
1297					note = n
1298					break
1299				end
1300			end
1301			if not note
1302				# We didn't find one with the data we're looking for, make
1303				# a new one.
1304				note = wspace.notes.new(conditions.merge(:data => data))
1305			end
1306		else
1307			# Otherwise, assume :insert, which means always make a new one
1308			note = wspace.notes.new
1309			if host
1310				note.host_id = host[:id]
1311			end
1312			if opts[:service] and opts[:service].kind_of? ::Mdm::Service
1313				note.service_id = opts[:service][:id]
1314			end
1315			note.seen     = seen
1316			note.critical = crit
1317			note.ntype    = ntype
1318			note.data     = data
1319		end
1320		msf_import_timestamps(opts,note)
1321		note.save!
1322		ret[:note] = note
1323	}
1324	end
1325
1326	#
1327	# This methods returns a list of all notes in the database
1328	#
1329	def notes(wspace=workspace)
1330	::ActiveRecord::Base.connection_pool.with_connection {
1331		wspace.notes
1332	}
1333	end
1334
1335	# This is only exercised by MSF3 XML importing for now. Needs the wait
1336	# conditions and return hash as well.
1337	def report_host_tag(opts)
1338		name = opts.delete(:name)
1339		raise DBImportError.new("Missing required option :name") unless name
1340		addr = opts.delete(:addr)
1341		raise DBImportError.new("Missing required option :addr") unless addr
1342		wspace = opts.delete(:wspace)
1343		raise DBImportError.new("Missing required option :wspace") unless wspace
1344	::ActiveRecord::Base.connection_pool.with_connection {
1345		if wspace.kind_of? String
1346			wspace = find_workspace(wspace)
1347		end
1348
1349		host = nil
1350		report_host(:workspace => wspace, :address => addr)
1351
1352
1353		host = get_host(:workspace => wspace, :address => addr)
1354		desc = opts.delete(:desc)
1355		summary = opts.delete(:summary)
1356		detail = opts.delete(:detail)
1357		crit = opts.delete(:crit)
1358		possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1)
1359		tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
1360		tag.name = name
1361		tag.desc = desc
1362		tag.report_summary = !!summary
1363		tag.report_detail = !!detail
1364		tag.critical = !!crit
1365		tag.hosts = tag.hosts | [host]
1366		tag.save! if tag.changed?
1367	}
1368	end
1369
1370	#
1371	# Store a set of credentials in the database.
1372	#
1373	# report_auth_info used to create a note, now it creates
1374	# an entry in the creds table. It's much more akin to
1375	# report_vuln() now.
1376	#
1377	# opts MUST contain
1378	# +:host+::   an IP address or Host object reference
1379	# +:port+::   a port number
1380	#
1381	# opts can contain
1382	# +:user+::   the username
1383	# +:pass+::   the password, or path to ssh_key
1384	# +:ptype+::  the type of password (password(ish), hash, or ssh_key)
1385	# +:proto+::  a transport name for the port
1386	# +:sname+::  service name
1387	# +:active+:: by default, a cred is active, unless explicitly false
1388	# +:proof+::  data used to prove the account is actually active.
1389	#
1390	# Sources: Credentials can be sourced from another credential, or from
1391	# a vulnerability. For example, if an exploit was used to dump the
1392	# smb_hashes, and this credential comes from there, the source_id would
1393	# be the Vuln id (as reported by report_vuln) and the type would be "Vuln".
1394	#
1395	# +:source_id+::   The Vuln or Cred id of the source of this cred.
1396	# +:source_type+:: Either Vuln or Cred
1397	#
1398	# TODO: This is written somewhat host-centric, when really the
1399	# Service is the thing. Need to revisit someday.
1400	def report_auth_info(opts={})
1401		return if not active
1402		raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
1403		raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?)
1404
1405		if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host]))
1406			raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})")
1407		end
1408
1409		host = opts.delete(:host)
1410		ptype = opts.delete(:type) || "password"
1411		token = [opts.delete(:user), opts.delete(:pass)]
1412		sname = opts.delete(:sname)
1413		port = opts.delete(:port)
1414		proto = opts.delete(:proto) || "tcp"
1415		proof = opts.delete(:proof)
1416		source_id = opts.delete(:source_id)
1417		source_type = opts.delete(:source_type)
1418		duplicate_ok = opts.delete(:duplicate_ok)
1419		# Nil is true for active.
1420		active = (opts[:active] || opts[:active].nil?) ? true : false
1421
1422		wspace = opts.delete(:workspace) || workspace
1423
1424		# Service management; assume the user knows what
1425		# he's talking about.
1426		service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace)
1427
1428		# Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables
1429		( token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[0]
1430		( token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[1]
1431
1432		ret = {}
1433
1434		#Check to see if the creds already exist. We look also for a downcased username with the
1435		#same password because we can fairly safely assume they are not in fact two seperate creds.
1436		#this allows us to hedge against duplication of creds in the DB.
1437
1438		if duplicate_ok
1439		# If duplicate usernames are okay, find by both user and password (allows
1440		# for actual duplicates to get modified updated_at, sources, etc)
1441			if token[0].nil? or token[0].empty?
1442				cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
1443			else
1444				cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
1445				unless cred
1446					dcu = token[0].downcase
1447					cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
1448					unless cred
1449						cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
1450					end
1451				end
1452			end
1453		else
1454			# Create the cred by username only (so we can change passwords)
1455			if token[0].nil? or token[0].empty?
1456				cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
1457			else
1458				cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype)
1459				unless cred
1460					dcu = token[0].downcase
1461					cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
1462					unless cred
1463						cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
1464					end
1465				end
1466			end
1467		end
1468
1469		# Update with the password
1470		cred.pass = (token[1] || "")
1471
1472		# Annotate the credential
1473		cred.ptype = ptype
1474		cred.active = active
1475
1476		# Update the source ID only if there wasn't already one.
1477		if source_id and !cred.source_id
1478			cred.source_id = source_id
1479			cred.source_type = source_type if source_type
1480		end
1481
1482		# Safe proof (lazy way) -- doesn't chop expanded
1483		# characters correctly, but shouldn't ever be a problem.
1484		unless proof.nil?
1485			proof = Rex::Text.to_hex_ascii(proof)
1486			proof = proof[0,4096]
1487		end
1488		cred.proof = proof
1489
1490		# Update the timestamp
1491		if cred.changed?
1492			msf_import_timestamps(opts,cred)
1493			cred.save!
1494		end
1495
1496		# Ensure the updated_at is touched any time report_auth_info is called
1497		# except when it's set explicitly (as it is for imports)
1498		unless opts[:updated_at] || opts["updated_at"]
1499			cred.updated_at = Time.now.utc
1500			cred.save!
1501		end
1502
1503		ret[:cred] = cred
1504	end
1505
1506	alias :report_cred :report_auth_info
1507	alias :report_auth :report_auth_info
1508
1509	#
1510	# Find or create a credential matching this type/data
1511	#
1512	def find_or_create_cred(opts)
1513		report_auth_info(opts)
1514	end
1515
1516	#
1517	# This method iterates the creds table calling the supplied block with the
1518	# cred instance of each entry.
1519	#
1520	def each_cred(wspace=workspace,&block)
1521	::ActiveRecord::Base.connection_pool.with_connection {
1522		wspace.creds.each do |cred|
1523			block.call(cred)
1524		end
1525	}
1526	end
1527
1528	def each_exploited_host(wspace=workspace,&block)
1529	::ActiveRecord::Base.connection_pool.with_connection {
1530		wspace.exploited_hosts.each do |eh|
1531			block.call(eh)
1532		end
1533	}
1534	end
1535
1536	#
1537	# Find or create a vuln matching this service/name
1538	#
1539	def find_or_create_vuln(opts)
1540		report_vuln(opts)
1541	end
1542
1543	#
1544	# opts MUST contain
1545	# +:host+:: the host where this vulnerability resides
1546	# +:name+:: the friendly name for this vulnerability (title)
1547	#
1548	# opts can contain
1549	# +:info+::   a human readable description of the vuln, free-form text
1550	# +:refs+::   an array of Ref objects or string names of references
1551	# +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
1552	#
1553	def report_vuln(opts)
1554		return if not active
1555		raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
1556		raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
1557		name = opts[:name] || return
1558		info = opts[:info]
1559
1560	::ActiveRecord::Base.connection_pool.with_connection {
1561
1562		wspace = opts.delete(:workspace) || workspace
1563		exploited_at = opts[:exploited_at] || opts["exploited_at"]
1564		details = opts.delete(:details)
1565		rids = opts.delete(:ref_ids)
1566
1567		if opts[:refs]
1568			rids ||= []
1569			opts[:refs].each do |r|
1570				if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
1571					r = "#{r.ctx_id}-#{r.ctx_val}"
1572				end
1573				rids << find_or_create_ref(:name => r)
1574			end
1575		end
1576
1577		host = nil
1578		addr = nil
1579		if opts[:host].kind_of? ::Mdm::Host
1580			host = opts[:host]
1581		else
1582			host = report_host({:workspace => wspace, :host => opts[:host]})
1583			addr = normalize_host(opts[:host])
1584		end
1585
1586		ret = {}
1587
1588		# Truncate the info field at the maximum field length
1589		if info
1590			info = info[0,65535]
1591		end
1592
1593		# Truncate the name field at the maximum field length
1594		name = name[0,255]
1595
1596		# Placeholder for the vuln object
1597		vuln = nil
1598
1599		# Identify the associated service
1600		service = opts.delete(:service)
1601
1602		# Treat port zero as no service
1603		if service or opts[:port].to_i > 0
1604
1605			if not service
1606				proto = nil
1607				case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
1608				when 'tcp','udp'
1609					proto = opts[:proto]
1610				when 'dns','snmp','dhcp'
1611					proto = 'udp'
1612					sname = opts[:proto]
1613				else
1614					proto = 'tcp'
1615					sname = opts[:proto]
1616				end
1617
1618				service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto)
1619			end
1620
1621			# Try to find an existing vulnerability with the same service & references
1622			# If there are multiple matches, choose the one with the most matches
1623			# If a match is found on a vulnerability with no associated service,
1624			# update that vulnerability with our service information. This helps
1625			# prevent dupes of the same vuln found by both local patch and
1626			# service detection.		
1627			if rids and rids.length > 0
1628				vuln = find_vuln_by_refs(rids, host, service)
1629				vuln.service = service if vuln
1630			end
1631		else
1632			# Try to find an existing vulnerability with the same host & references
1633			# If there are multiple matches, choose the one with the most matches
1634			if rids and rids.length > 0
1635				vuln = find_vuln_by_refs(rids, host)
1636			end
1637		end
1638
1639		# Try to match based on vuln_details records
1640		if not vuln and opts[:details_match]
1641			vuln = find_vuln_by_details(opts[:details_match], host, service)
1642			if vuln and service and not vuln.service
1643				vuln.service = service
1644			end
1645		end
1646
1647		# No matches, so create a new vuln record
1648		unless vuln
1649			if service
1650				vuln = service.vulns.find_by_name(name)
1651			else
1652				vuln = host.vulns.find_by_name(name)
1653			end
1654			
1655			unless vuln
1656
1657				vinf = {
1658					:host_id => host.id,
1659					:name    => name,
1660					:info    => info
1661				}
1662
1663				vinf[:service_id] = service.id if service 
1664				vuln = Mdm::Vuln.create(vinf)
1665			end
1666		end
1667
1668		# Set the exploited_at value if provided
1669		vuln.exploited_at = exploited_at if exploited_at
1670
1671		# Merge the references
1672		if rids
1673			vuln.refs << (rids - vuln.refs)
1674		end
1675
1676		# Finalize
1677		if vuln.changed?
1678			msf_import_timestamps(opts,vuln)
1679			vuln.save!
1680		end
1681
1682		# Handle vuln_details parameters
1683		report_vuln_details(vuln, details) if details
1684		
1685		vuln
1686	}
1687	end
1688
1689	def find_vuln_by_refs(refs, host, service=nil)
1690
1691		vuln = nil
1692
1693		# Try to find an existing vulnerability with the same service & references
1694		# If there are multiple matches, choose the one with the most matches
1695		if service
1696			refs_ids = refs.map{|x| x.id }
1697			vuln = service.vulns.find(:all, :include => [:refs], :conditions => { 'refs.id' => refs_ids }).sort { |a,b|
1698				( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
1699			}.first
1700		end
1701
1702		# Return if we matched based on service
1703		return vuln if vuln
1704
1705		# Try to find an existing vulnerability with the same host & references
1706		# If there are multiple matches, choose the one with the most matches
1707		refs_ids = refs.map{|x| x.id }
1708		vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b|
1709			( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
1710		}.first
1711
1712		return vuln
1713	end
1714
1715
1716	def find_vuln_by_details(details_map, host, service=nil)
1717
1718		# Create a modified version of the criteria in order to match against
1719		# the joined version of the fields
1720
1721		crit = {}
1722		details_map.each_pair do |k,v|
1723			crit[ "vuln_details.#{k}" ] = v
1724		end
1725
1726		vuln = nil
1727
1728		if service
1729			vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit)
1730		end
1731
1732		# Return if we matched based on service
1733		return vuln if vuln
1734
1735		# Prevent matches against other services
1736		crit["vulns.service_id"] = nil if service
1737		vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit)
1738
1739		return vuln
1740	end
1741
1742	def get_vuln(wspace, host, service, name, data='')
1743		raise RuntimeError, "Not workspace safe: #{caller.inspect}"
1744	::ActiveRecord::Base.connection_pool.with_connection {
1745		vuln = nil
1746		if (service)
1747			vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first()
1748		else
1749			vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first()
1750		end
1751
1752		return vuln
1753	}
1754	end
1755
1756	#
1757	# Find or create a reference matching this name
1758	#
1759	def find_or_create_ref(opts)
1760		ret = {}
1761		ret[:ref] = get_ref(opts[:name])
1762		return ret[:ref] if ret[:ref]
1763
1764	::ActiveRecord::Base.connection_pool.with_connection {
1765		ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name])
1766		if ref and ref.changed?
1767			ref.save!
1768		end
1769		ret[:ref] = ref
1770	}
1771	end
1772
1773	def get_ref(name)
1774	::ActiveRecord::Base.connection_pool.with_connection {
1775		::Mdm::Ref.find_by_name(name)
1776	}
1777	end
1778
1779	#
1780	# Populate the vuln_details table with additional
1781	# information, matched by a specific criteria
1782	#
1783	def report_vuln_details(vuln, details)
1784	::ActiveRecord::Base.connection_pool.with_connection {
1785		detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first
1786		if detail
1787			details.each_pair do |k,v|
1788				detail[k] = v
1789			end
1790			detail.save! if detail.changed?
1791			detail
1792		else
1793			detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id))
1794		end
1795	}
1796	end
1797
1798	#
1799	# Update vuln_details records en-masse based on specific criteria
1800	# Note that this *can* update data across workspaces
1801	#
1802	def update_vuln_details(details)
1803		criteria = details.delete(:key) || {}
1804		::Mdm::VulnDetail.update(key, details)
1805	end
1806
1807	#
1808	# Populate the host_details table with additional
1809	# information, matched by a specific criteria
1810	#
1811	def report_host_details(host, details)
1812	::ActiveRecord::Base.connection_pool.with_connection {
1813
1814		detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first
1815		if detail
1816			details.each_pair do |k,v|
1817				detail[k] = v
1818			end
1819			detail.save! if detail.changed?
1820			detail
1821		else
1822			detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id))
1823		end
1824	}
1825	end
1826
1827	# report_exploit() used to be used to track sessions and which modules
1828	# opened them. That information is now available with the session table
1829	# directly. TODO: kill this completely some day -- for now just warn if
1830	# some other UI is actually using it.
1831	def report_exploit(opts={})
1832		wlog("Deprecated method call: report_exploit()\n" +
1833			"report_exploit() options: #{opts.inspect}\n" +
1834			"report_exploit() call stack:\n\t#{caller.join("\n\t")}"
1835		)
1836	end
1837
1838	#
1839	# Deletes a host and associated data matching this address/comm
1840	#
1841	def del_host(wspace,

Large files files are truncated, but you can click here to view the full file