1
0
mirror of https://github.com/nmap/nmap.git synced 2026-01-06 06:29:03 +00:00

Include service detection information in Ndiff output. Change the output format

from

	21/tcp is open, was filtered.
	23/tcp is open, was filtered.
	80/tcp is open, was filtered.
	8701/tcp is filtered, was open.

to

	-21/tcp filtered  
	+21/tcp open ftp Netgear broadband router ftpd 1.0
	-23/tcp filtered  
	+23/tcp open telnet Netgear broadband router admin telnetd
	-80/tcp filtered  
	+80/tcp open http Embedded Allegro RomPager webserver 4.07 UPnP/1.0 (ZyXEL ZyWALL 2)
	-8701/tcp open unknown 
	+8701/tcp filtered
This commit is contained in:
david
2009-03-16 19:53:34 +00:00
parent 5e5d997335
commit fd41fcd0f7
4 changed files with 193 additions and 66 deletions

View File

@@ -22,15 +22,19 @@ Here is a sample of the text output:
Host is up, was unknown.
Add ipv4 address 10.214.143.33.
Add hostname cuvtdnray-504.example.com.
3389/tcp is open.
+3389/tcp open microsoft-rdp Microsoft Terminal Service
999 tcp ports are filtered.
scnqxez-842.example.com (10.189.71.117):
Remove hostname scnqxez-842.example.com.
10.226.19.80:
21/tcp is open, was filtered.
23/tcp is open, was filtered.
80/tcp is open, was filtered.
8701/tcp is filtered, was open.
-21/tcp filtered
+21/tcp open ftp Netgear broadband router ftpd 1.0
-23/tcp filtered
+23/tcp open telnet Netgear broadband router admin telnetd
-80/tcp filtered
+80/tcp open http Embedded Allegro RomPager webserver 4.07 UPnP/1.0 (ZyXEL ZyWALL 2)
-8701/tcp open unknown
+8701/tcp filtered
ywnleu-108.example.com (10.242.160.155):
Host is up, was unknown.
Add ipv4 address 10.242.160.155.
@@ -40,7 +44,7 @@ Here is a sample of the text output:
Host is unknown, was up.
Remove ipv4 address 10.65.53.252.
Remove hostname fiyrownc-307.example.com.
8089/tcp is unknown, was open.
-8089/tcp open upnp Microsoft Windows UPnP
999 tcp ports changed state from filtered to unknown.
Here is an abbreviated sample of the XML output:

View File

@@ -135,8 +135,25 @@ The port identified by the portid and protocol attributes changed state
from that given by the a-state attribute to that given by the b-state
attribute.
-->
<!ELEMENT port-state-change EMPTY>
<!ELEMENT port-state-change (a-service, b-service)?>
<!ATTLIST port-state-change portid CDATA #REQUIRED
protocol %protocol; #REQUIRED
a-state %port-state; #REQUIRED
b-state %port-state; #REQUIRED>
<!--
The service of a port from the A scan.
-->
<!ELEMENT a-service EMPTY>
<!ATTLIST a-service name CDATA #IMPLIED
product CDATA #IMPLIED
version CDATA #IMPLIED
extrainfo CDATA #IMPLIED>
<!--
Likewise for the B scan.
-->
<!ELEMENT b-service EMPTY>
<!ATTLIST b-service name CDATA #IMPLIED
product CDATA #IMPLIED
version CDATA #IMPLIED
extrainfo CDATA #IMPLIED>

View File

@@ -28,18 +28,19 @@ PORT_STATE_CHANGE_CONSOLIDATION_THRESHOLD = 10
PORT_STATE_CHANGE_DOUBLE_CONSOLIDATION_CHAR_THRESHOLD = 80
class Port(object):
"""A single port, consisting of a port specification and a state. A
specification, or "spec," is the 2-tuple (number, protocol). So (10, "tcp")
corresponds to the port 10/tcp. Port states are strings."""
"""A single port, consisting of a port specification, a state, and a service
version. A specification, or "spec," is the 2-tuple (number, protocol). So
(10, "tcp") corresponds to the port 10/tcp. Port states are strings."""
# This represents an "unknown" port state, the state a port is in when it
# has not been scanned. It must not compare equal with any real Nmap port
# state like "open", "closed", etc. For future compatibility's sake, always
# compare against Port.UNKNOWN, not the literal string "unknown".
UNKNOWN = "unknown"
def __init__(self, spec):
def __init__(self, spec, state = None):
self.spec = spec
self.state = Port.UNKNOWN
self.state = state or Port.UNKNOWN
self.service = Service()
def get_state_string(self):
return Port.state_to_string(self.state)
@@ -66,6 +67,43 @@ class PortDict(dict):
def __len__(self):
raise ValueError(u"__len__ is not defined for objects of type PortDict.")
class Service(object):
"""A service version as determined by -sV scan. Also contains the looked-up
port name if -sV wasn't used."""
def __init__(self):
self.name = None
self.product = None
self.version = None
self.extrainfo = None
# self.hostname = None
# self.ostype = None
# self.devicetype = None
# self.tunnel = None
def __eq__(self, other):
return self.name == other.name \
and self.product == other.product \
and self.version == other.version \
and self.extrainfo == other.extrainfo
def to_string(self):
"""Get a string like in the SERVICE column of Nmap output."""
if self.name is None:
return u""
else:
return self.name
def version_to_string(self):
"""Get a string like in the VERSION column of Nmap output."""
parts = []
if self.product is not None:
parts.append(self.product)
if self.version is not None:
parts.append(self.version)
if self.extrainfo is not None:
parts.append(u"(%s)" % self.extrainfo)
return u" ".join(parts)
class Host(object):
"""A single host, with a state (unknown, up, or down), addresses, and a
dict mapping port specs to Ports."""
@@ -112,10 +150,8 @@ class Host(object):
return unicode(id(self))
def add_port(self, spec, state):
"""Add a port in the given state."""
port = self.ports[spec]
port.state = state
def add_port(self, port):
self.ports[port.spec] = port
def swap_ports(self, spec_a, spec_b):
"""Swap the ports given by the two specs. This is used when a service is
@@ -295,28 +331,48 @@ class PortIdChangeHunk(DiffHunk):
return frag
class PortStateChangeHunk(DiffHunk):
def __init__(self, spec, a_state, b_state):
def __init__(self, spec, a_port, b_port):
self.spec = spec
self.a_state = a_state
self.b_state = b_state
self.a_port = a_port
self.b_port = b_port
def to_string(self):
if self.a_state == Port.UNKNOWN:
return u"%s is %s." % (Port.spec_to_string(self.spec), self.b_state)
else:
return u"%s is %s, was %s." % (Port.spec_to_string(self.spec), self.b_state, self.a_state)
lines = []
a_str = u"%s %s" % (self.a_port.service.to_string(), self.a_port.service.version_to_string())
b_str = u"%s %s" % (self.b_port.service.to_string(), self.b_port.service.version_to_string())
if self.a_port.state != Port.UNKNOWN:
lines.append("-%s %s %s" % (Port.spec_to_string(self.a_port.spec), self.a_port.state, a_str))
if self.b_port.state != Port.UNKNOWN:
lines.append("+%s %s %s" % (Port.spec_to_string(self.b_port.spec), self.b_port.state, b_str))
return u"\n".join(lines)
def service_elem(service, document, name):
"""Create a service element."""
elem = document.createElement(name)
if service.name is not None:
elem.setAttribute(u"name", service.name)
if service.product is not None:
elem.setAttribute(u"product", service.product)
if service.version is not None:
elem.setAttribute(u"version", service.version)
if service.extrainfo is not None:
elem.setAttribute(u"extrainfo", service.extrainfo)
return elem
service_elem = staticmethod(service_elem)
def to_dom_fragment(self, document):
frag = document.createDocumentFragment()
elem = document.createElement(u"port-state-change")
elem.setAttribute(u"portid", unicode(self.spec[0]))
elem.setAttribute(u"protocol", self.spec[1])
elem.setAttribute(u"a-state", self.a_state)
elem.setAttribute(u"b-state", self.b_state)
elem.setAttribute(u"a-state", self.a_port.state)
elem.setAttribute(u"b-state", self.b_port.state)
frag.appendChild(elem)
if not self.a_port.service == self.b_port.service:
elem.appendChild(self.service_elem(self.a_port.service, document, u"a-service"))
elem.appendChild(self.service_elem(self.b_port.service, document, u"b-service"))
return frag
def partition_port_state_changes(diff):
"""Partition a list of PortStateChangeHunks into equivalence classes
based on the tuple (protocol, a_state, b_state). The partition is returned
@@ -325,8 +381,8 @@ def partition_port_state_changes(diff):
for hunk in diff:
if not isinstance(hunk, PortStateChangeHunk):
continue
a_state = hunk.a_state
b_state = hunk.b_state
a_state = hunk.a_port.state
b_state = hunk.b_port.state
protocol = hunk.spec[1]
transitions.setdefault((protocol, a_state, b_state), []).append(hunk)
return transitions.values()
@@ -373,10 +429,10 @@ class ScanDiff(object):
h_diff_copy = h_diff[:]
cons_port_state_changes = consolidate_port_state_changes(h_diff_copy, PORT_STATE_CHANGE_CONSOLIDATION_THRESHOLD)
for hunk in h_diff_copy:
print >> f, u"\t" + hunk.to_string();
print >> f, u"\n".join(u"\t" + s for s in hunk.to_string().split(u"\n"));
for group in cons_port_state_changes:
a_state = group[0].a_state
b_state = group[0].b_state
a_state = group[0].a_port.state
b_state = group[0].b_port.state
protocol = group[0].spec[1]
port_list = [hunk.spec[0] for hunk in group]
port_list_string = render_port_list(port_list)
@@ -428,8 +484,8 @@ def port_diff(a, b):
if a.spec != b.spec:
hunk = PortIdChangeHunk(a.spec, b.spec)
diff.append(hunk)
if a.state != b.state:
hunk = PortStateChangeHunk(b.spec, a.state, b.state)
if not (a.state == b.state and a.service == b.service):
hunk = PortStateChangeHunk(b.spec, a, b)
diff.append(hunk)
return diff
@@ -558,7 +614,7 @@ class NmapContentHandler(xml.sax.handler.ContentHandler):
self.scanned_ports = {}
self.current_host = None
self.current_extraports = []
self.current_spec = None
self.current_port = None
def parent_element(self):
"""Return the name of the element containing the current one, or None if
@@ -654,17 +710,26 @@ class NmapContentHandler(xml.sax.handler.ContentHandler):
except KeyError:
warn(u"port element of host %s missing the \"protocol\" attribute; skipping." % self.current_host.format_name())
return
self.current_spec = portid, protocol
self.current_port = Port((portid, protocol))
elif name == u"state":
assert self.parent_element() == u"port"
assert self.current_host is not None
if self.current_spec is None:
if self.current_port is None:
return
if not attrs.has_key(u"state"):
warn("state element of port %s is missing the \"state\" attribute; assuming \"unknown\"." % Port.spec_to_string(self.current_spec))
warn("state element of port %s is missing the \"state\" attribute; assuming \"unknown\"." % Port.spec_to_string(self.current_port.spec))
return
state = attrs[u"state"]
self.current_host.add_port(self.current_spec, state)
self.current_port.state = attrs[u"state"]
self.current_host.add_port(self.current_port)
elif name == u"service":
assert self.parent_element() == u"port"
assert self.current_host is not None
if self.current_port is None:
return
self.current_port.service.name = attrs.get(u"name")
self.current_port.service.product = attrs.get(u"product")
self.current_port.service.version = attrs.get(u"version")
self.current_port.service.extrainfo = attrs.get(u"extrainfo")
elif name == u"finished":
assert self.parent_element() == u"runstats"
if attrs.has_key(u"time"):
@@ -689,12 +754,12 @@ class NmapContentHandler(xml.sax.handler.ContentHandler):
if spec in known_specs:
continue
assert self.current_host.ports[spec].state == Port.UNKNOWN
self.current_host.add_port(spec, extraports_state)
self.current_host.add_port(Port(spec, state = extraports_state))
self.current_host = None
self.current_extraports = []
elif name == u"port":
self.current_spec = None
self.current_port = None
def usage():
print u"""\

View File

@@ -77,9 +77,9 @@ class partition_port_state_changes_test(unittest.TestCase):
for host, h_diff in self.diff:
partition = partition_port_state_changes(h_diff)
for group in partition:
key = (group[0].spec[1], group[0].a_state, group[0].b_state)
key = (group[0].spec[1], group[0].a_port.state, group[0].b_port.state)
for hunk in group:
self.assertTrue(key == (hunk.spec[1], hunk.a_state, hunk.b_state))
self.assertTrue(key == (hunk.spec[1], hunk.a_port.state, hunk.b_port.state))
class consolidate_port_state_changes_test(unittest.TestCase):
"""Test the consolidate_port_state_changes function."""
@@ -151,6 +151,34 @@ class port_diff_test(unittest.TestCase):
diff = port_diff(a, b)
self.assertTrue(len(diff) > 1)
class service_test(unittest.TestCase):
"""Test the Service class."""
def test_to_string(self):
serv = Service()
self.assertTrue(serv.to_string() == u"")
serv.name = u"ftp"
self.assertTrue(serv.to_string() == serv.name)
def test_version_to_string(self):
serv = Service()
self.assertTrue(serv.version_to_string() == u"")
serv = Service()
serv.product = u"FooBar"
self.assertTrue(len(serv.version_to_string()) > 0)
serv = Service()
serv.version = u"1.2.3"
self.assertTrue(len(serv.version_to_string()) > 0)
serv = Service()
serv.extrainfo = u"misconfigured"
self.assertTrue(len(serv.version_to_string()) > 0)
serv = Service()
serv.product = u"FooBar"
serv.version = u"1.2.3"
# Must match Nmap output.
self.assertTrue(serv.version_to_string() == u"%s %s" % (serv.product, serv.version))
serv.extrainfo = u"misconfigured"
self.assertTrue(serv.version_to_string() == u"%s %s (%s)" % (serv.product, serv.version, serv.extrainfo))
class host_test(unittest.TestCase):
"""Test the Host class."""
def test_empty(self):
@@ -178,15 +206,27 @@ class host_test(unittest.TestCase):
spec = (10, "tcp")
port = h.ports[spec]
self.assertTrue(port.state == Port.UNKNOWN, "Port state is %s, expected %s." % (port.get_state_string(), "unknown"))
h.add_port(spec, "open")
h.add_port(Port(spec, "open"))
self.assertTrue(len(h.get_known_ports()) == 1)
port = h.ports[spec]
self.assertTrue(port.state == "open", "Port state is %s, expected %s." % (port.get_state_string(), "open"))
h.add_port(spec, "closed")
h.add_port(Port(spec, "closed"))
self.assertTrue(len(h.get_known_ports()) == 1)
port = h.ports[spec]
self.assertTrue(port.state == "closed", "Port state is %s, expected %s." % (port.get_state_string(), "closed"))
spec = (22, "tcp")
port = h.ports[spec]
self.assertTrue(port.state == Port.UNKNOWN, "Port state is %s, expected %s." % (port.get_state_string(), "unknown"))
port = Port(spec)
port.state = "open"
port.service.name = "ssh"
h.add_port(port)
self.assertTrue(len(h.get_known_ports()) == 2)
port = h.ports[spec]
self.assertTrue(port.state == "open", "Port state is %s, expected %s." % (port.get_state_string(), "open"))
self.assertTrue(port.service.name == "ssh", "Port service.name is %s, expected %s." % (port.service.name, "ssh"))
def test_swap_ports(self):
h = Host()
spec_a = (10, "tcp")
@@ -196,13 +236,13 @@ class host_test(unittest.TestCase):
self.assertTrue(h.ports[spec_b].state == Port.UNKNOWN)
self.assertTrue(h.ports[spec_a].spec == spec_a)
self.assertTrue(h.ports[spec_b].spec == spec_b)
h.add_port(spec_a, "open")
h.add_port(Port(spec_a, "open"))
h.swap_ports(spec_a, spec_b)
self.assertTrue(h.ports[spec_a].state == Port.UNKNOWN)
self.assertTrue(h.ports[spec_b].state == "open")
self.assertTrue(h.ports[spec_a].spec == spec_a)
self.assertTrue(h.ports[spec_b].spec == spec_b)
h.add_port(spec_a, "closed")
h.add_port(Port(spec_a, "closed"))
h.swap_ports(spec_a, spec_b)
self.assertTrue(h.ports[spec_a].state == "open")
self.assertTrue(h.ports[spec_b].state == "closed")
@@ -227,8 +267,9 @@ def host_apply_diff(host, diff):
host.swap_ports(hunk.a_spec, hunk.b_spec)
elif isinstance(hunk, PortStateChangeHunk):
port = host.ports[hunk.spec]
assert port.state == hunk.a_state
host.add_port(hunk.spec, hunk.b_state)
assert port.state == hunk.a_port.state
host.add_port(Port(hunk.spec, hunk.b_port.state))
host.ports[hunk.spec].service = hunk.b_port.service
else:
assert False
@@ -245,8 +286,8 @@ class host_diff_test(unittest.TestCase):
def test_self(self):
h = Host()
h.add_port((10, "tcp"), "open")
h.add_port((22, "tcp"), "closed")
h.add_port(Port((10, "tcp"), "open"))
h.add_port(Port((22, "tcp"), "closed"))
diff = host_diff(h, h)
self.assertTrue(len(diff) == 0)
@@ -277,8 +318,8 @@ class host_diff_test(unittest.TestCase):
a = Host()
b = Host()
spec = (10, "tcp")
a.add_port(spec, "open")
b.add_port(spec, "closed")
a.add_port(Port(spec, "open"))
b.add_port(Port(spec, "closed"))
diff = host_diff(a, b)
self.assertTrue(len(diff) > 0)
for hunk in diff:
@@ -287,7 +328,7 @@ class host_diff_test(unittest.TestCase):
def test_port_state_change_unknown(self):
a = Host()
b = Host()
b.add_port((10, "tcp"), "open")
b.add_port(Port((10, "tcp"), "open"))
diff = host_diff(a, b)
self.assertTrue(len(diff) > 0)
for hunk in diff:
@@ -300,12 +341,12 @@ class host_diff_test(unittest.TestCase):
def test_port_state_change_multi(self):
a = Host()
b = Host()
a.add_port((10, "tcp"), "open")
a.add_port((20, "tcp"), "closed")
a.add_port((30, "tcp"), "open")
b.add_port((10, "tcp"), "open")
b.add_port((20, "tcp"), "open")
b.add_port((30, "tcp"), "open")
a.add_port(Port((10, "tcp"), "open"))
a.add_port(Port((20, "tcp"), "closed"))
a.add_port(Port((30, "tcp"), "open"))
b.add_port(Port((10, "tcp"), "open"))
b.add_port(Port((20, "tcp"), "open"))
b.add_port(Port((30, "tcp"), "open"))
diff = host_diff(a, b)
self.assertTrue(len(diff) > 0)
for hunk in diff:
@@ -377,12 +418,12 @@ class host_diff_test(unittest.TestCase):
the hosts become the same."""
a = Host()
b = Host()
a.add_port((10, "tcp"), "open")
a.add_port((20, "tcp"), "closed")
a.add_port((40, "udp"), "open|filtered")
b.add_port((10, "tcp"), "open")
b.add_port((30, "tcp"), "open")
a.add_port((40, "udp"), "open")
a.add_port(Port((10, "tcp"), "open"))
a.add_port(Port((20, "tcp"), "closed"))
a.add_port(Port((40, "udp"), "open|filtered"))
b.add_port(Port((10, "tcp"), "open"))
b.add_port(Port((30, "tcp"), "open"))
a.add_port(Port((40, "udp"), "open"))
a.hostnames = ["a", "localhost"]
a.hostnames = ["b", "localhost", "b.example.com"]
diff = host_diff(a, b)