Permalink
Browse files

Added report support

use garmr -h to see new options
- added a switch to control which reporter is used, and a switch to
control the output filename
- reporter objects can be specified via the module name and class name,
so if a loaded module contains a reporter subclass, it can be invoked
through the -r switch (e.g. -r reporter.AntXmlReporter), or via a
registered shortcut (-r xml)
- fixed some bugs
  • Loading branch information...
1 parent 82d5942 commit 3e130b412b98dc562994196aef36fda1033c51e3 @ygjb ygjb committed Sep 19, 2011
Showing with 147 additions and 14 deletions.
  1. +2 −2 corechecks.py
  2. +18 −2 garmr.py
  3. +87 −2 reporter.py
  4. +40 −8 scanner.py
View
4 corechecks.py
@@ -14,7 +14,7 @@ def analyze(self, response):
else:
result = self.result("Fail", "HttpOnly is not set", response.headers[cookieheader])
else:
- result = self.result("Pass", "No cookie is set by this request.", None)
+ result = self.result("Skip", "No cookie is set by this response.", None)
return result
class SecureAttributePresent(PassiveTest):
@@ -35,7 +35,7 @@ def analyze(self, response):
else:
result = self.result("Pass", "The secure attribute is not set (expected for HTTP)", response.headers[cookieheader])
else:
- result = self.result("Pass", "No cookie is set by this request.", None)
+ result = self.result("Skip", "No cookie is set by this response.", None)
return result
View
20 garmr.py
@@ -1,6 +1,7 @@
import argparse
from scanner import ActiveTest, PassiveTest, Scanner
import corechecks
+from reporter import Reporter
import sys
import traceback
@@ -11,15 +12,17 @@ def main():
parser.add_argument("-f", "--file", action="append", dest="target_files", help="File with urls to test")
parser.add_argument("-p", "--force-passive", action="store_true", default=False, dest="force_passives", help ="Force passives to be run for each active test")
parser.add_argument("-d", "--dns", action="store_false", default=True, dest="resolve_target", help ="Skip DNS resolution when registering a target.")
+ parser.add_argument("-r", "--report", action="store", default="reporter.AntXmlReporter", dest="report",help="Load a reporter, format module.class, e.g. reporter.AntXmlReporter")
+ parser.add_argument("-o", "--output", action="store", default="garmr-results.xml", dest="output", help="Default output is garmr-results.xml")
#todo add option to influence DNS resolution before scanning.
args = parser.parse_args()
-
- print "Garmr v0.02"
scanner = Scanner()
scanner.force_passives = args.force_passives
scanner.resolve_target = args.resolve_target
+ scanner.output = args.output
+
if args.targets != None:
for target in args.targets:
@@ -47,6 +50,19 @@ def main():
except:
Scanner.logger.fatal("Unable to load the requested module [%s]", module)
quit()
+
+ try:
+ reporter = args.report.split('.')
+ if len(reporter) == 1:
+ scanner.reporter = Reporter.reporters[reporter[0]]
+ else:
+ scanner.reporter = getattr(sys.modules[reporter[0]], reporter[1])()
+ Scanner.logger.info("Writing report to [%s] using [%s]" % (args.output, args.report))
+ if isinstance(scanner.reporter, Reporter) == False:
+ raise Exception("Cannot configure a non-scanner object!")
+ except Exception, e:
+ Scanner.logger.fatal("Unable to use the reporter class [%s]: %s", args.report, e)
+ quit()
scanner.run_scan()
View
89 reporter.py
@@ -1,6 +1,9 @@
class Reporter():
+
+ reporters = {}
+
# todo - implement me!
def start_report(self):
return None
@@ -11,6 +14,12 @@ def start_targets(self):
def write_target(self, target):
return None
+ def start_actives(self):
+ return None
+
+ def write_active(self, test):
+ return None
+
def start_passives(self):
return None
@@ -20,15 +29,91 @@ def write_passive(self, target):
def end_passives(self):
return None
+ def end_actives(self):
+ return None
+
def end_targets(self):
return None
def end_report(self):
- return None
+ return "This reporter is unimplemented!"
+
+class DetailReporter(Reporter):
+ # TODO Implement me.
+ def end_report(self):
+ return "This reporter should emit an XML report that includes all of the the details for each test. including captured data"
-class XmlReporter():
+Reporter.reporters['detail'] = DetailReporter()
+
+class AntXmlReporter(Reporter):
+ def __init__(self):
+ self.report = ""
+ self.errtypes = { 'Error' : "error", 'Fail' : "failure", 'Skip' : "skipped"}
+
def start_report(self):
+ self.report = '<?xml version="1.0" encoding="utf-8"?>\n'
+
+ return None
+
+ def start_targets(self):
+ self.report += "<testsuites>\n"
+ return None
+
+ def write_target(self, target):
+ self.states = {}
+ self.states["Skip"] = 0
+ self.states["Error"] = 0
+ self.states["Pass"] = 0
+ self.states["Fail"] = 0
+ self.checks = 0
+ self.current_target = target
+ self.lines = ""
+ return None
+
+ def start_actives(self):
+ return None
+
+ def write_active(self, test, result):
+ self.states[result["state"]] += 1
+ self.checks += 1
+ module, check = ("%s" % test ).split('.')
+ self.lines += '\t\t<testcase classname="%s" name="%s" time="%s"' % (module, check, result["duration"])
+ if result["state"] == "Pass":
+ self.lines += " />\n"
+ else:
+ self.lines += '>\n\t\t\t<{errtype}>{message}</{errtype}>\n\t\t</testcase>\n'.format(errtype=self.errtypes[result["state"]], message=result["message"])
+ return None
+
+ def start_passives(self):
+ return None
+
+ def write_passive(self, test, result):
+ self.states[result["state"]] += 1
+ self.checks += 1
+ module, check = ("%s" % test ).split('.')
+ self.lines += '\t\t<testcase classname="%s" name="%s" time="%s"' % (module, check, result["duration"])
+ if result["state"] == "Pass":
+ self.lines += " />\n"
+ else:
+ self.lines += '>\n\t\t\t<{errtype}>{message}</{errtype}>\n\t\t</testcase>\n'.format(errtype=self.errtypes[result["state"]], message=result["message"])
+ return None
+
+ def end_passives(self):
return None
+ def end_actives(self):
+ self.report+= '\t<testsuite name="{target}" errors="{errors}" failures="{failures}" skips="{skips}" tests="{checks}" time="{duration}">\n{lines}\t</testsuite>\n'.format(
+ target = self.current_target, errors=self.states["Error"], failures = self.states["Fail"],
+ skips = self.states["Skip"], checks = self.checks, duration=100, lines=self.lines)
+ return None
+
+ def end_targets(self):
+ self.report += "</testsuites>\n"
+ return None
+
+ def end_report(self):
+ return self.report
+
+Reporter.reporters['xml'] = AntXmlReporter()
View
48 scanner.py
@@ -69,50 +69,82 @@ def __init__(self):
self.reporter = Reporter()
def scan_target(self, target):
+ self.reporter.write_target(target)
Scanner.logger.info("[%s] scanning:" % target)
url = urlparse(target)
is_ssl = url.scheme == "https"
results = {}
-
+ self.reporter.start_actives()
for test in self._active_tests_:
if (test.secure_only and not is_ssl):
Scanner.logger.info("\t[Skip] [%s] (reason: secure_only)" % test.__class__)
+ result = ActiveTest().result("Skip", "This check is only applicable to SSL requests", None)
+ result['start'] = datetime.now()
+ result['end'] = result['start']
+ result['duration'] = 0
+ results[test.__class__]
continue
elif (test.insecure_only and is_ssl):
Scanner.logger.info("\t[Skip] [%s] (reason: insecure_only)" % test.__class__)
+ result = ActiveTest().result("Skip", "This check is only applicable to SSL requests", None)
+ result['start'] = datetime.now()
+ result['end'] = result['start']
+ result['duration'] = 0
+ results[test.__class__] = result
continue
start = datetime.now()
o = test.execute(target)
result = o[0]
response = o[1]
end = datetime.now()
td = end - start
- duration = float((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6)) / 10**6
result['start'] = start
result['end'] = end
- result['duration'] = duration
+ result['duration'] = float((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6)) / 10**6
Scanner.logger.info("\t[%s] %s %s" % (test.__class__, result['state'], result['message']))
+ self.reporter.write_active(test.__class__, result)
if (result['state'] == "Error"):
Scanner.logger.error(result['data'])
if response != None and test.run_passives:
result['passive'] = {}
+ self.reporter.start_passives()
for passive in self._passive_tests_:
if passive.secure_only and not is_ssl:
Scanner.logger.debug("\t\t[%s] Skip Test invalid for http scheme" % passive.__class__)
- result["passive"][self.__class__] = PassiveTest().result("Skip", "This check is only applicable to SSL requests.", None)
- continue
- passive_result = passive.analyze(response)
- Scanner.logger.info("\t\t[%s] %s %s" % (passive.__class__, passive_result['state'], passive_result['message']))
+ passive_result = PassiveTest().result("Skip", "This check is only applicable to SSL requests.", None)
+ start = datetime.now()
+ passive_result['start'] = start
+ passive_result['end'] = start
+ passive_result["duration"] = 0
+ else:
+ start = datetime.now()
+ passive_result = passive.analyze(response)
+ end = datetime.now()
+ td = end - start
+ passive_result['start'] = start
+ passive_result['end'] = end
+ passive_result['duration'] = float((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6)) / 10**6
+ Scanner.logger.info("\t\t[%s] %s %s" % (passive.__class__, passive_result['state'], passive_result['message']))
result["passive"][passive.__class__] = passive_result
+ self.reporter.write_passive(passive.__class__,passive_result)
+ self.reporter.end_passives()
results[test.__class__] = result
+ self.reporter.end_actives()
return results
def run_scan(self):
+ results = {}
+ self.reporter.start_report()
+ self.reporter.start_targets()
for target in self._targets_:
try:
- self.scan_target(target)
+ results[target] = self.scan_target(target)
except:
Scanner.logger.error(traceback.format_exc())
+ self.reporter.end_targets()
+ file = open(self.output, "w")
+ file.write(self.reporter.end_report())
+ file.close()
def register_target(self, url):

0 comments on commit 3e130b4

Please sign in to comment.