Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

New features, bugfixes

* Module support works
* Added HttpOnly, Secure attribute checks
* Added djangochecks module
* Enabled dns override switch
* Added support for target files
* Added support
  • Loading branch information...
commit 00a906c9de75952eada070e6ae555791546cca70 1 parent 447cabe
@ygjb ygjb authored
View
6 .gitignore
@@ -3,3 +3,9 @@ dist/
*.egg-info/
*.pyc
garmr-results.xml
+
+.project
+
+.pydevproject
+
+targets.txt
View
0  README.md
No changes.
View
55 corechecks.py
@@ -2,19 +2,55 @@
import requests
from scanner import ActiveTest, PassiveTest, Scanner, get_url
+
+class HttpOnlyPresent(PassiveTest):
+
+ def analyze(self, response):
+ cookieheader = "Set-Cookie"
+ has_cookie = cookieheader in response.headers
+ if has_cookie:
+ if "httponly" in response.headers[cookieheader].lower():
+ result = self.result("Pass", "HttpOnly is set", response.headers[cookieheader])
+ 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)
+ return result
+
+class SecureAttributePresent(PassiveTest):
+
+ def analyze(self, response):
+ url = urlparse(response.url)
+ cookieheader = "Set-Cookie"
+ has_cookie = cookieheader in response.headers
+ if has_cookie:
+ if "httponly" in response.headers[cookieheader].lower():
+ if url.scheme == "https":
+ result = self.result("Pass", "HttpOnly is set", response.headers[cookieheader])
+ else:
+ result = self.result("Fail", "HttpOnly should only be set for cookies sent over SSL.", response.headers[cookieheader])
+ else:
+ if url.scheme == "https":
+ result = self.result("Fail", "HttpOnly is not set", response.headers[cookieheader])
+ 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)
+ return result
+
+
class StsHeaderPresent(PassiveTest):
secure_only = True
- stsheader = "Strict-Transport-Security"
def analyze(self, response):
-
- sts = selfstsheader in response.headers
+ stsheader = "Strict-Transport-Security"
+ sts = stsheader in response.headers
if sts == False:
result = self.result("Fail", "STS header not found.", None)
else:
- result = self.result("Pass", "STS header present.", response.headers[self.stsheader])
+ result = self.result("Pass", "STS header present.", response.headers[stsheader])
return result
class XfoPresent(PassiveTest):
@@ -45,24 +81,25 @@ def do_test(self, url):
return (result, response);
class StsUpgradeCheck(ActiveTest):
+ insecure_only = True
run_passives = False
description = "Inspect the STS redirect process."
- stsheader = "Strict-Transport-Security"
def do_test(self, url):
+ stsheader = "Strict-Transport-Security"
u = urlparse(url)
if u.scheme == "http":
correct_header = False
bad_redirect = False
response1 = get_url(url, False)
- invalid_header = self.stsheader in response1.headers
+ invalid_header = stsheader in response1.headers
is_redirect = response1.status_code == 301
if is_redirect == True:
redirect = response1.headers["location"]
r = urlparse(redirect)
if r.scheme == "https":
response2 = get_url(redirect, False)
- correct_header = self.stsheader in response2.headers
+ correct_header = stsheader in response2.headers
else:
bad_redirect = True
@@ -86,4 +123,6 @@ def configure(scanner):
scanner.register_test(StsHeaderPresent())
scanner.register_test(XfoPresent())
scanner.register_test(RobotsTest())
- scanner.register_test(StsUpgradeCheck())
+ scanner.register_test(StsUpgradeCheck())
+ scanner.register_test(HttpOnlyPresent())
+ scanner.register_test(SecureAttributePresent())
View
24 djangochecks.py
@@ -0,0 +1,24 @@
+from urlparse import urlparse
+import requests
+from scanner import ActiveTest, PassiveTest, Scanner, get_url
+
+
+class AdminAvailable(ActiveTest):
+ run_passives = True
+
+ def do_test(self, url):
+ u = urlparse(url)
+ adminurl="%s://%s/admin" % (u.scheme, u.netloc)
+ response = requests.get(adminurl)
+ if response.status_code == 200:
+ result = self.result("Pass", "Django admin page is present.", response.content)
+ else:
+ result = self.result("Fail", "Default Django admin page is not present ", None)
+ return (result, response);
+
+
+def configure(scanner):
+ if isinstance(scanner, Scanner) == False:
+ raise Exception("Cannot configure a non-scanner object!")
+ scanner.register_test(AdminAvailable())
+
View
42 garmr.py
@@ -1,11 +1,16 @@
import argparse
from scanner import ActiveTest, PassiveTest, Scanner
import corechecks
+import sys
+import traceback
def main():
parser = argparse.ArgumentParser(description='Check urls for compliance with Secure Coding Guidelines')
parser.add_argument("-u", "--url", action="append", dest="targets", help="add a target to test")
- parser.add_argument("-m", "--module", action="append", dest="modules", help="load a test suite module")
+ parser.add_argument("-m", "--module", action="append", dest="modules", help="load a test suite")
+ 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.")
#todo add option to influence DNS resolution before scanning.
args = parser.parse_args()
@@ -13,17 +18,36 @@ def main():
print "Garmr v0.02"
scanner = Scanner()
+ scanner.force_passives = args.force_passives
+ scanner.resolve_target = args.resolve_target
-
- for target in args.targets:
- scanner.register_target(target)
+ if args.targets != None:
+ for target in args.targets:
+ scanner.register_target(target)
+
+ if args.target_files != None:
+ for targets in args.target_files:
+ try:
+ f = open(targets, "r")
+ for target in f:
+ t = target.strip()
+ if len(t) > 0:
+ scanner.register_target(t)
+ except:
+ Scanner.logger.error("Unable to process the target list in: %s", targets)
corechecks.configure(scanner)
- '''
- implement module loading here.
- modules are collections of classes that extend ActiveTest and PassiveTest
- the module must implement the configure(Scanner) function which will register the exposed functions to the scanner.
- '''
+
+ if args.modules != None:
+ for module in args.modules:
+ try:
+ __import__(module)
+ m = sys.modules[module]
+ m.configure(scanner)
+ except:
+ Scanner.logger.fatal("Unable to load the requested module [%s]", module)
+ quit()
+
scanner.run_scan()
View
30 scanner.py
@@ -59,6 +59,8 @@ class Scanner():
logger.setLevel(logging.DEBUG)
def __init__(self):
+ self.resolve_target = True
+ self.force_passives = False
self._passive_tests_ = []
self._active_tests_ = []
self._targets_ = []
@@ -77,7 +79,7 @@ def scan_target(self, target):
Scanner.logger.info("\t[Skip] [%s] (reason: secure_only)" % test.__class__)
continue
elif (test.insecure_only and is_ssl):
- Scanner.logger.info("\t[Skip] [%s] (reason: insecure_only" % test.__class__)
+ Scanner.logger.info("\t[Skip] [%s] (reason: insecure_only)" % test.__class__)
continue
start = datetime.now()
o = test.execute(target)
@@ -107,25 +109,27 @@ def scan_target(self, target):
def run_scan(self):
for target in self._targets_:
- # try:
- self.scan_target(target)
-# except Exception, e:
-# print "kaboom? %s" % e
+ try:
+ self.scan_target(target)
+ except:
+ Scanner.logger.error(traceback.format_exc())
+
def register_target(self, url):
u = urlparse(url)
valid = u.netloc != "" and u.scheme in self._protos_
reason = "%s%s" % ("[bad netloc]" if u.netloc == "" else "", "" if u.scheme in self._protos_ else "[bad scheme]")
- # todo - allow an option to disable dns resolution since address resolution may fail, but a host may
- # still be reachable through a proxy
+
# todo - support ipv6 urls
host = u.netloc.split(':')[0]
- try:
- socket.getaddrinfo(host, None)
- except socket.gaierror:
- valid = False
- reason = "%s[dns]" % reason
-
+ if (self.resolve_target):
+ try:
+ socket.getaddrinfo(host, None)
+ except socket.gaierror:
+ valid = False
+ reason = "%s[dns]" % reason
+ else:
+ valid = True
if valid:
self._targets_.append(url)
Scanner.logger.debug("[target]: %s" % url)
Please sign in to comment.
Something went wrong with that request. Please try again.