# -*- coding: binary -*- require "rex/parser/nokogiri_doc_mixin" require 'msf/core' module Rex module Parser # If Nokogiri is available, define the document class. load_nokogiri && class CIDocument < Nokogiri::XML::SAX::Document include NokogiriDocMixin attr_reader :text def initialize(*args) super(*args) @state[:has_text] = true end # Triggered every time a new element is encountered. We keep state # ourselves with the @state variable, turning things on when we # get here (and turning things off when we exit in end_element()). def start_element(name=nil,attrs=[]) attrs = normalize_attrs(attrs) block = @block r = { :e => name } attrs.each { |pair| r[pair[0]] = pair[1] } if @state[:path] @state[:path].push r end case name when "entity" @state[:path] = [ r ] record_device(r) when "property" return if not @state[:address] return if not @state[:props] @state[:props] << [ r["type"], r["key"]] end end # When we exit a tag, this is triggered. def end_element(name=nil) block = @block case name when "entity" # Wrap it up if @state[:address] host_object = report_host &block report_services(host_object) report_vulns(host_object) end # Reset the state once we close a host @report_data = {:wspace => @args[:wspace]} @state[:root] = {} when "property" if @state[:props] @text.strip! if @text process_property @state[:props].pop end end @state[:path].pop @text = nil end def record_device(info) if info["class"] and info["class"] == "host" and info["name"] address = info["name"].to_s.gsub(/^.*\//, '') return if address !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ @state[:address] = address @state[:props] = [] end end def process_property return if not @state[:props] return if not @state[:props].length > 0 @state[:root] ||= {} @cobj = @state[:root] property_parser(0) end def property_parser(idx) return if not @state[:props][idx] case @state[:props][idx][0] when "container", "ports", "entity", "properties" @cobj[ @state[:props][idx][1] ] ||= {} @cobj = @cobj[ @state[:props][idx][1] ] else @cobj[ state[:props][idx][1] ] = @text end property_parser(idx + 1) end def report_host(&block) @report_data = { :ports => [:ignore], :state => Msf::HostState::Alive, :host => @state[:address] } if @state[:root]["dns names"] and @state[:root]["dns names"].keys.length > 0 @report_data[:name] = @state[:root]["dns names"].keys.first end if host_is_okay @report_data.delete(:ports) db.emit(:address, @report_data[:host],&block) if block host_object = db_report(:host, @report_data.merge( :workspace => @args[:wspace] ) ) if host_object db.report_import_note(host_object.workspace, host_object) end host_object end end def report_services(host_object) return unless host_object.kind_of? ::Mdm::Host snames = {} ( @state[:root]["services"] || {} ).each_pair do |sname, sinfo| sinfo.each_pair do |pinfo,pdata| snames[pinfo] = sname.dup end end reported = [] if @state[:root]["tcp_ports"] @state[:root]["tcp_ports"].each_pair do |pn, ps| ps = "open" if ps == "listen" svc = { :port => pn.to_i, :state => ps, :proto => 'tcp'} if @state[:root]["Banners"] and @state[:root]["Banners"][pn.to_s] svc[:info] = @state[:root]["Banners"][pn.to_s] end svc[:name] = snames["#{pn}-tcp"] if snames["#{pn}-tcp"] reported << db_report(:service, svc.merge(:host => host_object)) end end if @state[:root]["udp_ports"] @state[:root]["udp_ports"].each_pair do |pn, ps| ps = "open" if ps == "listen" svc = { :port => pn.to_i, :state => ps, :proto => 'udp'} svc[:name] = snames["#{pn}-udp"] if snames["#{pn}-tcp"] reported << db_report(:service, svc.merge(:host => host_object)) end end ( @state[:root]["services"] || {} ).each_pair do |sname, sinfo| sinfo.each_pair do |pinfo,pdata| sport,sproto = pinfo.split("-") db_report(:note, { :host => host_object, :port => sport.to_i, :proto => sproto, :ntype => "ci.#{sname}.fingerprint", :data => pdata }) end end reported end def report_vulns(host_object) vuln_count = 0 block = @block return unless host_object.kind_of? ::Mdm::Host return unless @state[:root]["Vulnerabilities"] @state[:root]["Vulnerabilities"].each_pair do |cve, vinfo| vinfo.each_pair do |vname, vdesc| data = { :workspace => host_object.workspace, :host => host_object, :name => vname, :info => vdesc, :refs => [ cve ] } db_report(:vuln, data) end end end end end end