2012-06-29 05:18:28 +00:00
|
|
|
# -*- coding: binary -*-
|
2011-05-25 18:48:23 +00:00
|
|
|
module Rex
|
2013-08-30 21:28:33 +00:00
|
|
|
module Parser
|
|
|
|
|
|
|
|
# Determines if Nokogiri is available and if it's a minimum
|
|
|
|
# acceptable version.
|
|
|
|
def self.load_nokogiri
|
|
|
|
@nokogiri_loaded = false
|
|
|
|
begin
|
|
|
|
require 'nokogiri'
|
|
|
|
major,minor = Nokogiri::VERSION.split(".")[0,2]
|
|
|
|
if major.to_i >= 1
|
|
|
|
if minor.to_i >= 4
|
|
|
|
@nokogiri_loaded = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
rescue LoadError => e
|
|
|
|
@nokogiri_loaded = false
|
|
|
|
@nokogiri_error = e
|
|
|
|
end
|
|
|
|
@nokogiri_loaded
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.nokogiri_loaded
|
|
|
|
!!@nokogiri_loaded
|
|
|
|
end
|
|
|
|
|
|
|
|
# Useful during development, shouldn't be used in normal operation.
|
|
|
|
def self.reload(fname)
|
|
|
|
$stdout.puts "Reloading #{fname}..."
|
|
|
|
load __FILE__
|
|
|
|
load File.join(File.expand_path(File.dirname(__FILE__)),fname)
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2011-05-26 20:29:47 +00:00
|
|
|
end
|
2011-05-25 18:48:23 +00:00
|
|
|
|
2011-05-26 20:29:47 +00:00
|
|
|
module Rex
|
|
|
|
module Parser
|
2011-05-25 18:48:23 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
load_nokogiri && module NokogiriDocMixin
|
|
|
|
|
|
|
|
# Set up the getters and instance variables for the document
|
|
|
|
eval("attr_reader :args, :db, :state, :block, :report_data")
|
|
|
|
|
|
|
|
def initialize(args,db,&block)
|
|
|
|
@args = args
|
|
|
|
@db = db
|
|
|
|
@state = {}
|
|
|
|
@state[:current_tag] = {}
|
|
|
|
@block = block if block
|
|
|
|
@report_data = {:wspace => args[:wspace]}
|
|
|
|
@nx_console_id = args[:nx_console_id]
|
|
|
|
super()
|
|
|
|
end
|
|
|
|
|
|
|
|
# Turn XML attribute pairs in to more workable hashes (there
|
|
|
|
# are better Enumerable tricks in Ruby 1.9, but ignoring for now)
|
|
|
|
def attr_hash(attrs)
|
|
|
|
h = {}
|
|
|
|
attrs.each {|k,v| h[k] = v}
|
|
|
|
h
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid_ip(addr)
|
|
|
|
valid = false
|
|
|
|
valid = ::Rex::Socket::RangeWalker.new(addr).valid? rescue false
|
|
|
|
!!valid
|
|
|
|
end
|
|
|
|
|
|
|
|
def normalize_ref(ref_type, ref_value)
|
|
|
|
return if ref_type.nil? || ref_type.empty? || ref_value.nil? || ref_value.empty?
|
|
|
|
ref_value = ref_value.strip
|
|
|
|
ref_type = ref_type.strip.upcase
|
|
|
|
|
|
|
|
ret = case ref_type
|
|
|
|
when "CVE"
|
|
|
|
ref_value.gsub("CAN", "CVE")
|
|
|
|
when "MS"
|
|
|
|
if ref_value =~ /^MS[0-9]/
|
|
|
|
"MSB-#{ref_value}"
|
|
|
|
else
|
|
|
|
"MSB-MS#{ref_value}"
|
|
|
|
end
|
|
|
|
when "URL", "BID"
|
|
|
|
"#{ref_type}-#{ref_value}"
|
|
|
|
when "APPLE"
|
|
|
|
ref_value
|
|
|
|
when "XF"
|
|
|
|
if ref_value =~ /\((\d+)\)$/
|
|
|
|
"#{ref_type}-#{$1}"
|
|
|
|
else
|
|
|
|
"#{ref_type}-#{ref_value}"
|
|
|
|
end
|
|
|
|
else # Handle others?
|
|
|
|
"#{ref_type}-#{ref_value}"
|
|
|
|
end
|
|
|
|
return ret
|
|
|
|
end
|
|
|
|
|
|
|
|
def normalize_references(orig_refs)
|
|
|
|
return [] unless orig_refs
|
|
|
|
refs = []
|
|
|
|
orig_refs.each do |ref_hash|
|
|
|
|
|
|
|
|
ref_hash_sym = Hash[ref_hash.map {|k, v| [k.to_sym, v] }]
|
|
|
|
ref_type = ref_hash_sym[:source].to_s.strip.upcase
|
|
|
|
ref_value = ref_hash_sym[:value].to_s.strip
|
|
|
|
refs << normalize_ref(ref_type, ref_value)
|
|
|
|
end
|
|
|
|
return refs.compact.uniq
|
|
|
|
end
|
|
|
|
|
|
|
|
def in_tag(tagname)
|
|
|
|
@state[:current_tag].keys.include? tagname
|
|
|
|
end
|
|
|
|
|
|
|
|
# If there's an address, it's not on the blacklist,
|
|
|
|
# it has ports, and the port list isn't
|
|
|
|
# empty... it's okay.
|
|
|
|
def host_is_okay
|
|
|
|
return false unless @report_data[:host]
|
|
|
|
return false unless valid_ip(@report_data[:host])
|
|
|
|
return false unless @report_data[:state] == Msf::HostState::Alive
|
|
|
|
if @args[:blacklist]
|
|
|
|
return false if @args[:blacklist].include?(@report_data[:host])
|
|
|
|
end
|
|
|
|
return false unless @report_data[:ports]
|
|
|
|
return false if @report_data[:ports].empty?
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
# XXX: Document classes ought to define this
|
|
|
|
def determine_port_state(v)
|
|
|
|
return v
|
|
|
|
end
|
|
|
|
|
|
|
|
# Circumvent the unknown attribute logging by the various reporters. They
|
|
|
|
# seem to be there just for debugging anyway.
|
|
|
|
def db_report(table, data)
|
|
|
|
raise "Data should be a hash" unless data.kind_of? Hash
|
|
|
|
nonempty_data = data.reject {|k,v| v.nil?}
|
|
|
|
valid_attrs = db_valid_attributes(table)
|
|
|
|
raise "Unknown table `#{table}'" if valid_attrs.empty?
|
|
|
|
case table
|
|
|
|
when :note, :web_site, :web_page, :web_form, :web_vuln
|
|
|
|
just_the_facts = nonempty_data
|
|
|
|
else
|
|
|
|
just_the_facts = nonempty_data.select {|k,v| valid_attrs.include? k.to_s.to_sym}
|
2013-05-23 18:33:19 +00:00
|
|
|
end
|
|
|
|
return nil if just_the_facts.empty?
|
|
|
|
just_the_facts[:task] = @args[:task]
|
2013-08-30 21:28:33 +00:00
|
|
|
db.send("report_#{table}", just_the_facts)
|
|
|
|
end
|
|
|
|
|
|
|
|
# XXX: It would be better to either have a single registry of acceptable
|
|
|
|
# keys if we're going to alert on bad ones, or to be more forgiving if
|
|
|
|
# the caller is this thing. There is basically no way to tell if
|
|
|
|
# report_host()'s tastes are going to change with this scheme.
|
|
|
|
def db_valid_attributes(table)
|
|
|
|
case table.to_s.to_sym
|
|
|
|
when :host
|
|
|
|
::Mdm::Host.new.attribute_names.map {|x| x.to_sym} |
|
|
|
|
[:host, :workspace]
|
|
|
|
when :service
|
|
|
|
::Mdm::Service.new.attribute_names.map {|x| x.to_sym} |
|
|
|
|
[:host, :host_name, :mac, :workspace]
|
|
|
|
when :vuln
|
|
|
|
::Mdm::Vuln.new.attribute_names.map {|x| x.to_sym} |
|
|
|
|
[:host, :refs, :workspace, :port, :proto, :details, :exploited_at]
|
|
|
|
when :vuln_details
|
|
|
|
::Mdm::VulnDetails.new.attribute_names.map {|x| x.to_sym} | [ :key ]
|
|
|
|
when :host_details
|
|
|
|
::Mdm::HostDetails.new.attribute_names.map {|x| x.to_sym} | [ :key ]
|
|
|
|
when :note, :web_site, :web_page, :web_form, :web_vuln
|
|
|
|
# These guys don't complain
|
|
|
|
[:anything]
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Nokogiri 1.4.4 (and presumably beyond) generates attrs as pairs,
|
|
|
|
# like [["value1","foo"],["value2","bar"]] (but not hashes for some
|
|
|
|
# reason). 1.4.3.1 (and presumably 1.4.3.x and prior) generates attrs
|
|
|
|
# as a flat array of strings. We want array_pairs.
|
|
|
|
def normalize_attrs(attrs)
|
|
|
|
attr_pairs = []
|
|
|
|
case attrs.first
|
|
|
|
when Array, NilClass
|
|
|
|
attr_pairs = attrs
|
|
|
|
when String
|
|
|
|
attrs.each_index {|i|
|
|
|
|
next if i % 2 == 0
|
|
|
|
attr_pairs << [attrs[i-1],attrs[i]]
|
|
|
|
}
|
|
|
|
else # Wow, yet another format! It's either from the distant past or distant future.
|
|
|
|
raise ::Msf::DBImportError.new("Unknown format for XML attributes. Please check your Nokogiri version.")
|
|
|
|
end
|
|
|
|
return attr_pairs
|
|
|
|
end
|
|
|
|
|
|
|
|
# This breaks xml-encoded characters, so need to append.
|
|
|
|
# It's on the end_element tag name to turn the appending
|
|
|
|
# off and clear out the data.
|
|
|
|
def characters(text)
|
|
|
|
return unless @state[:has_text]
|
|
|
|
@text ||= ""
|
|
|
|
@text << text
|
|
|
|
end
|
|
|
|
|
|
|
|
# Effectively the same as characters()
|
|
|
|
def cdata_block(text)
|
|
|
|
return unless @state[:has_text]
|
|
|
|
@text ||= ""
|
|
|
|
@text << text
|
|
|
|
end
|
|
|
|
|
|
|
|
def end_document
|
|
|
|
block = @block
|
|
|
|
return unless @report_type_ok
|
|
|
|
unless @state[:current_tag].empty?
|
|
|
|
missing_ends = @state[:current_tag].keys.map {|x| "'#{x}'"}.join(", ")
|
|
|
|
msg = "Warning, the provided file is incomplete, and there may be missing\n"
|
|
|
|
msg << "data. The following tags were not closed: #{missing_ends}."
|
|
|
|
db.emit(:warning,msg,&block) if block
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2011-05-26 20:29:47 +00:00
|
|
|
|
|
|
|
end
|
2011-05-25 18:48:23 +00:00
|
|
|
end
|