Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/parser/nmap_xml.rb
27991 views
1
# -*- coding: binary -*-
2
3
require 'rexml/document'
4
require 'rexml/streamlistener'
5
6
module Rex
7
module Parser
8
9
#
10
# Stream parser for nmap -oX xml output
11
#
12
# Yields a hash representing each host found in the xml stream. Each host
13
# will look something like the following:
14
# {
15
# "status" => "up",
16
# "addrs" => { "ipv4" => "192.168.0.1", "mac" => "00:0d:87:a1:df:72" },
17
# "ports" => [
18
# { "portid" => "22", "state" => "closed", ... },
19
# { "portid" => "80", "state" => "open", ... },
20
# ...
21
# ]
22
# }
23
#
24
# Usage:
25
# parser = NmapXMLStreamParser.new { |host|
26
# # do stuff with the host
27
# }
28
# REXML::Document.parse_stream(File.new(nmap_xml), parser)
29
# -- or --
30
# parser = NmapXMLStreamParser.new
31
# parser.on_found_host = Proc.new { |host|
32
# # do stuff with the host
33
# }
34
# REXML::Document.parse_stream(File.new(nmap_xml), parser)
35
#
36
# This parser does not maintain state as well as a tree parser, so malformed
37
# xml will trip it up. Nmap shouldn't ever output malformed xml, so it's not
38
# a big deal.
39
#
40
class NmapXMLStreamParser
41
include REXML::StreamListener
42
43
#
44
# Callback for processing each found host
45
#
46
attr_accessor :on_found_host
47
48
#
49
# Create a new stream parser for NMAP XML output
50
#
51
# If given a block, it will be stored in +on_found_host+, otherwise you
52
# need to set it explicitly, e.g.:
53
# parser = NmapXMLStreamParser.new
54
# parser.on_found_host = Proc.new { |host|
55
# # do stuff with the host
56
# }
57
# REXML::Document.parse_stream(File.new(nmap_xml), parser)
58
#
59
def initialize(&block)
60
reset_state
61
on_found_host = block if block
62
end
63
64
def reset_state
65
@host = { "status" => nil, "addrs" => {}, "ports" => [], "scripts" => {} }
66
@state = nil
67
end
68
69
def tag_start(name, attributes)
70
begin
71
case name
72
when "address"
73
@host["addrs"][attributes["addrtype"]] = attributes["addr"]
74
if (attributes["addrtype"] =~ /ipv[46]/)
75
@host["addr"] = attributes["addr"]
76
end
77
when "osclass"
78
# If there is more than one, take the highest accuracy. In case of
79
# a tie, this will have the effect of taking the last one in the
80
# list. Last is really no better than first but nmap appears to
81
# put OSes in chronological order, at least for Windows.
82
# Accordingly, this will report XP instead of 2000, 7 instead of
83
# Vista, etc, when each has the same accuracy.
84
if (@host["os_accuracy"].to_i <= attributes["accuracy"].to_i)
85
@host["os_vendor"] = attributes["vendor"]
86
@host["os_family"] = attributes["osfamily"]
87
@host["os_version"] = attributes["osgen"]
88
@host["os_accuracy"] = attributes["accuracy"]
89
end
90
when "osmatch"
91
if(attributes["accuracy"].to_i == 100)
92
@host["os_match"] = attributes["name"]
93
end
94
when "uptime"
95
@host["last_boot"] = attributes["lastboot"]
96
when "hostname"
97
if(attributes["type"] == "PTR")
98
@host["reverse_dns"] = attributes["name"]
99
end
100
when "status"
101
# <status> refers to the liveness of the host; values are "up" or "down"
102
@host["status"] = attributes["state"]
103
@host["status_reason"] = attributes["reason"]
104
when "port"
105
@host["ports"].push(attributes)
106
when "state"
107
# <state> refers to the state of a port; values are "open", "closed", or "filtered"
108
@host["ports"].last["state"] = attributes["state"]
109
when "service"
110
# Store any service and script info with the associated port. There shouldn't
111
# be any collisions on attribute names here, so just merge them.
112
@host["ports"].last.merge!(attributes)
113
when "script"
114
# Associate scripts under a port tag with the appropriate port.
115
# Other scripts from <hostscript> tags can only be associated with
116
# the host and scripts from <postscript> tags don't really belong
117
# to anything, so ignore them
118
if @state == :in_port_tag
119
@host["ports"].last["scripts"] ||= {}
120
@host["ports"].last["scripts"][attributes["id"]] = attributes["output"]
121
elsif @host
122
@host["scripts"] ||= {}
123
@host["scripts"][attributes["id"]] = attributes["output"]
124
else
125
# post scripts are used for things like comparing all the found
126
# ssh keys to see if multiple hosts have the same key
127
# fingerprint. Ignore them.
128
end
129
when "trace"
130
@host["trace"] = {"port" => attributes["port"], "proto" => attributes["proto"], "hops" => [] }
131
when "hop"
132
if @host["trace"]
133
@host["trace"]["hops"].push(attributes)
134
end
135
end
136
rescue NoMethodError => err
137
raise err unless err.message =~ /NilClass/
138
end
139
end
140
141
def tag_end(name)
142
case name
143
when "port"
144
@state = nil
145
when "host"
146
on_found_host.call(@host) if on_found_host
147
reset_state
148
end
149
end
150
end
151
152
end
153
end
154
155
156