Skip to content

Commit 56bfa31

Browse files
authored
Merge pull request #319 from patriknordlen/nmap
nmap XML file parsing support
2 parents 31c1786 + b7c2177 commit 56bfa31

File tree

8 files changed

+89
-16
lines changed

8 files changed

+89
-16
lines changed

docs/features.rst

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -389,18 +389,19 @@ DefectDojo has the ability to import reports from other security tools. Current
389389

390390
1. Burp XML
391391
2. Nessus (CSV, XML)
392-
3. Nexpose XML 2.0
393-
4. ZAP XML
394-
5. Veracode Detailed XML Report
395-
6. Checkmarx Detailed XML Report
396-
7. AppSpider Vulnerabilities Summary XML Report (VulnerabilitiesSummary.xml)
397-
8. Arachni Scanner JSON Report
398-
9. Visual Code Grepper XML or CSV
399-
10. OWASP Dependency Check XML
400-
11. Retire.js JavaScript Scan JSON
401-
12. Node Security Platform JSON
402-
12. Qualys XML
403-
13. Generic Findings in CSV format
392+
3. Nmap (XML)
393+
4. Nexpose XML 2.0
394+
5. ZAP XML
395+
6. Veracode Detailed XML Report
396+
7. Checkmarx Detailed XML Report
397+
8. AppSpider Vulnerabilities Summary XML Report (VulnerabilitiesSummary.xml)
398+
9. Arachni Scanner JSON Report
399+
10. Visual Code Grepper XML or CSV
400+
11. OWASP Dependency Check XML
401+
12. Retire.js JavaScript Scan JSON
402+
13. Node Security Platform JSON
403+
14. Qualys XML
404+
15. Generic Findings in CSV format
404405

405406

406407
The importers analyze each report and create new Findings for each item reported. DefectDojo collapses duplicate

dojo/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class Meta:
228228

229229

230230
class ImportScanForm(forms.Form):
231-
SCAN_TYPE_CHOICES = (("Burp Scan", "Burp Scan"), ("Nessus Scan", "Nessus Scan"), ("Nexpose Scan", "Nexpose Scan"),
231+
SCAN_TYPE_CHOICES = (("Burp Scan", "Burp Scan"), ("Nessus Scan", "Nessus Scan"), ("Nmap Scan", "Nmap Scan"), ("Nexpose Scan", "Nexpose Scan"),
232232
("AppSpider Scan", "AppSpider Scan"), ("Veracode Scan", "Veracode Scan"),
233233
("Checkmarx Scan", "Checkmarx Scan"), ("ZAP Scan", "ZAP Scan"),
234234
("Arachni Scan", "Arachni Scan"), ("VCG Scan", "VCG Scan"),

dojo/models.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,12 @@ class CWE(models.Model):
416416

417417
class Endpoint(models.Model):
418418
protocol = models.CharField(null=True, blank=True, max_length=10,
419-
help_text="The communication protocl such as 'http', 'ftp', etc.")
419+
help_text="The communication protocol such as 'http', 'ftp', etc.")
420420
host = models.CharField(null=True, blank=True, max_length=500,
421421
help_text="The host name or IP address, you can also include the port number. For example"
422422
"'127.0.0.1', '127.0.0.1:8080', 'localhost', 'yourdomain.com'.")
423+
fqdn = models.CharField(null=True, blank=True, max_length=500)
424+
port = models.IntegerField(null=True, blank=True, help_text="The network port associated with the endpoint.")
423425
path = models.CharField(null=True, blank=True, max_length=500,
424426
help_text="The location of the resource, it should start with a '/'. For example"
425427
"/endpoint/420/edit")
@@ -438,14 +440,19 @@ def __unicode__(self):
438440
from urlparse import uses_netloc
439441

440442
netloc = self.host
443+
fqdn = self.fqdn
444+
port = self.port
441445
scheme = self.protocol
442446
url = self.path if self.path else ''
443447
query = self.query
444448
fragment = self.fragment
445449

450+
if port:
451+
netloc += ':%s' % port
452+
446453
if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'):
447454
if url and url[:1] != '/': url = '/' + url
448-
if scheme:
455+
if scheme and scheme in uses_netloc and url[:2] != '//':
449456
url = '//' + (netloc or '') + url
450457
else:
451458
url = (netloc or '') + url

dojo/templates/dojo/import_scan_results.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<li><b>Burp XML</b> - When the Burp report is generated, we recommend selecting the option to Base64 encode both the request and
1919
response fields. These fields will be processed and made available in the Finding view page.</li>
2020
<li><b>Tenable Nessus</b> - Reports can be imported in the CSV, and .nessus (XML) report formats.</li>
21+
<li><b>Nmap</b> - XML output (use -oX)</li>
2122
<li><b>Rapid7 Nexpose XML 2.0</b> - Use the full XML export template from Nexpose.</li>
2223
<li><b>Rapid7 AppSpider</b> - Use the VulnerabilitiesSummary.xml file found in the zipped report download.</li>
2324
<li><b>Veracode Detailed XML Report</b></li>

dojo/templates/dojo/view_finding.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ <h4>JIRA Link
198198
<div class="col-md-12">
199199
<div class="panel panel-default endpoints table-responsive">
200200
<div class="panel-heading">
201-
<h4>Vulnerable Endpoints / Systems
201+
<h4>Affected Endpoints / Systems
202202
<span class="pull-right"><a data-toggle="collapse" href="#vuln_endpoints"><i
203203
class="glyphicon glyphicon-chevron-up"></i></a></span>
204204
</h4>

dojo/tools/factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dojo.tools.burp.parser import BurpXmlParser
22
from dojo.tools.nessus.parser import NessusCSVParser, NessusXMLParser
3+
from dojo.tools.nmap.parser import NmapXMLParser
34
from dojo.tools.nexpose.parser import NexposeFullXmlParser
45
from dojo.tools.veracode.parser import VeracodeXMLParser
56
from dojo.tools.zap.parser import ZapXmlParser
@@ -27,6 +28,8 @@ def import_parser_factory(file, test):
2728
parser = NessusCSVParser(file, test)
2829
elif filename.endswith("xml") or filename.endswith("nessus"):
2930
parser = NessusXMLParser(file, test)
31+
elif scan_type == "Nmap Scan":
32+
parser = NmapXMLParser(file, test)
3033
elif scan_type == "Nexpose Scan":
3134
parser = NexposeFullXmlParser(file, test)
3235
elif scan_type == "Veracode Scan":

dojo/tools/nmap/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = 'patriknordlen'

dojo/tools/nmap/parser.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from xml.dom import NamespaceErr
2+
import lxml.etree as le
3+
import os
4+
import csv
5+
import re
6+
from dojo.models import Endpoint, Finding
7+
from pprint import pprint
8+
9+
__author__ = 'patriknordlen'
10+
11+
class NmapXMLParser(object):
12+
def __init__(self, file, test):
13+
parser = le.XMLParser(resolve_entities=False)
14+
nscan = le.parse(file, parser)
15+
root = nscan.getroot()
16+
17+
if 'nmaprun' not in root.tag:
18+
raise NamespaceErr("This doesn't seem to be a valid Nmap xml file.")
19+
dupes = {}
20+
for host in root.iter("host"):
21+
ip = host.find("address[@addrtype='ipv4']").attrib['addr']
22+
fqdn = host.find("hostnames/hostname[@type='PTR']").attrib['name'] if host.find("hostnames/hostname[@type='PTR']") is not None else None
23+
24+
for portelem in host.xpath("ports/port[state/@state='open']"):
25+
port = portelem.attrib['portid']
26+
protocol = portelem.attrib['protocol']
27+
28+
title = "Open port: %s/%s" % (port, protocol)
29+
30+
description = "%s:%s A service was found to be listening on this port." % (ip, port)
31+
32+
if portelem.find('service') is not None:
33+
if hasattr(portelem.find('service'),'product'):
34+
serviceinfo = " (%s%s)" % (portelem.find('service').attrib['product'], " "+portelem.find('service').attrib['version'] if hasattr(portelem.find('service'),'version') else "")
35+
else:
36+
serviceinfo = ""
37+
description += " It was identified as '%s%s'." % (portelem.find('service').attrib['name'], serviceinfo)
38+
description += '\n\n'
39+
40+
severity = "Info"
41+
42+
dupe_key = port
43+
44+
if dupe_key in dupes:
45+
find = dupes[dupe_key]
46+
if description is not None:
47+
find.description += description
48+
else:
49+
find = Finding(title=title,
50+
test=test,
51+
active=False,
52+
verified=False,
53+
description=description,
54+
severity=severity,
55+
numerical_severity=Finding.get_numerical_severity(severity))
56+
find.unsaved_endpoints = list()
57+
dupes[dupe_key] = find
58+
59+
find.unsaved_endpoints.append(Endpoint(host=ip, fqdn=fqdn, port=port, protocol=protocol))
60+
self.items = dupes.values()

0 commit comments

Comments
 (0)