Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
Merge branch 'master' of github.com:AutomatedTester/Garmr
Browse files Browse the repository at this point in the history
  • Loading branch information
AutomatedTester committed Nov 26, 2011
2 parents ea9678d + 082f274 commit 61416f3
Show file tree
Hide file tree
Showing 11 changed files with 666 additions and 429 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Expand Up @@ -3,3 +3,9 @@ dist/
*.egg-info/ *.egg-info/
*.pyc *.pyc
garmr-results.xml garmr-results.xml

.project

.pydevproject

targets.txt
39 changes: 22 additions & 17 deletions README.md
Expand Up @@ -4,29 +4,34 @@


# Garmr # Garmr


Garmr is a checking that a site meets the basic requirements from a security point of view. Garmr is a tool to inspect the responses from websites for basic security requirements.
It checks what the correct HTTP calls are allowed and others are blocked. It is installable from PyPi.
Garmr includes a set of core test cases implemented in corechecks that are derived from
the Secure Coding Guidelines that can be found at [https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines]


## Installation ## Installation


To install it is a simple case of This version of Garmr :
sudo pip install garmr * does not support pip. Grab the source from git
* requires Requests > 0.6.2-dev, which can be installed by following the instructions here:
** http://docs.python-requests.org/en/latest/user/install/#get-the-code


## Usage ## Usage


garmr -u http://application.under.test/path usage: garmr.py [-h] [-u TARGETS] [-m MODULES] [-f TARGET_FILES] [-p] [-d]

This will create a file called garmr-results.xml which will have the results of the
tests stored in it.

### Options

* "-u", "--url": Url to be tested
* "-f", "--file": File name with URLS to test, Currently not available
* "-x", "--xunit": Name of file that you wish to write to. Defaults to garmr-results.xml


optional arguments:
-h, --help show this help message and exit
-u TARGETS, --url TARGETS
add a target to test
-m MODULES, --module MODULES
load a test suite
-f TARGET_FILES, --file TARGET_FILES
File with urls to test
-p, --force-passive Force passives to be run for each active test
-d, --dns Skip DNS resolution when registering a target.


## Tasks ## Tasks

* Implement sequences (i.e. a series of ActiveTests that once invoked, maintains a cookie jar until the list of URLs is exhausted)
If you want to see what is currently being worked on you can see it on the * Implement a proper detailed reporter; currently a range of data is accumulated, but never reported.
[Pivotal Tracker](https://www.pivotaltracker.com/projects/285905) * Implement more checks
1 change: 0 additions & 1 deletion __init__.py

This file was deleted.

29 changes: 29 additions & 0 deletions config.txt
@@ -0,0 +1,29 @@
[Garmr]
force-passives = False
module = corechecks, djangochecks
reporter = reporter.AntXmlReporter
output = garmr-results.xml
dns = True

[corechecks.StsUpgradeCheck]
enabled = True

[djangochecks.AdminAvailable]
enabled = True
path = console

[corechecks.RobotsTest]
enabled = True

[corechecks.StsHeaderPresent]
enabled = True

[corechecks.SecureAttributePresent]
enabled = True

[corechecks.HttpOnlyPresent]
enabled = True

[corechecks.XfoPresent]
enabled = True

125 changes: 125 additions & 0 deletions corechecks.py
@@ -0,0 +1,125 @@
from urlparse import urlparse
import requests
from scanner import ActiveTest, PassiveTest, Scanner, get_url


class HttpOnlyAttributePresent(PassiveTest):
description = "Inspect the Set-Cookie: header and determine if the HttpOnly attribute is present."
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("Skip", "No cookie is set by this response.", None)
return result

class SecureAttributePresent(PassiveTest):
description = "Inspect the Set-Cookie: header and determine if the Secure attribute is present."
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("Skip", "No cookie is set by this response.", None)
return result


class StrictTransportSecurityPresent(PassiveTest):
secure_only = True
description = "Check if the Strict-Transport-Security header is present in TLS requests."
def analyze(self, response):
stsheader = "Strict-Transport-Security"
sts = stsheader in response.headers
if sts == False:
result = self.result("Fail", "Strict-Transport-Security header not found.", None)
else:
result = self.result("Pass", "Strict-Transport-Security header present.", response.headers[stsheader])
return result

class XFrameOptionsPresent(PassiveTest):
description = "Check if X-Frame-Options header is present."
def analyze(self, response):
xfoheader = "X-Frame-Options"
xfo = xfoheader in response.headers
if xfo == False:
result = self.result("Fail", "X-Frame-Options header not found.", None)
else:
result = self.result("Pass", "X-Frame-Options header present.", response.headers[xfoheader])
return result

class RobotsTest(ActiveTest):
run_passives = True
description = "Check for the presence of a robots.txt file. If save_contents is true, the contents will be saved."
config = {"save_contents" : "False"}
def do_test(self, url):
u = urlparse(url)
roboturl="%s://%s/robots.txt" % (u.scheme, u.netloc)
response = requests.get(roboturl)
if response.status_code == 200:
result = self.result("Pass", "A robots.txt file is present on the server",
response.content if self.config["save_contents"].lower() == "true" else None)
else:
result = self.result("Fail", "No robots.txt file was found.", None)
return (result, response);

class StsUpgradeCheck(ActiveTest):
insecure_only = True
run_passives = False
description = "Inspect the Strict-Transport-Security redirect process according to http://tools.ietf.org/html/draft-hodges-strict-transport-sec"

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 = 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 = stsheader in response2.headers
else:
bad_redirect = True

success = invalid_header == False and is_redirect == True and correct_header == True
if success == True:
message = "The STS upgrade occurs properly (no STS header on HTTP, a 301 redirect, and an STS header in the subsequent request."
else:
message = "%s%s%s%s" % (
"The initial HTTP response included an STS header (RFC violation)." if invalid_header else "",
"" if is_redirect else "The initial HTTP response should be a 301 redirect (RFC violation see ).",
"" if correct_header else "The followup to the 301 redirect must include the STS header.",
"The 301 location must use the https scheme." if bad_redirect else ""
)
result = self.result("Pass" if success else "Fail", message, None)
return (result, response1)


def configure(scanner):
if isinstance(scanner, Scanner) == False:
raise Exception("Cannot configure a non-scanner object!")
scanner.register_check(StrictTransportSecurityPresent())
scanner.register_check(XFrameOptionsPresent())
scanner.register_check(RobotsTest())
scanner.register_check(StsUpgradeCheck())
scanner.register_check(HttpOnlyAttributePresent())
scanner.register_check(SecureAttributePresent())
25 changes: 25 additions & 0 deletions djangochecks.py
@@ -0,0 +1,25 @@
from urlparse import urlparse
import requests
from scanner import ActiveTest, PassiveTest, Scanner, get_url


class AdminAvailable(ActiveTest):
run_passives = True
config = {"path" : "admin"}

def do_test(self, url):
u = urlparse(url)
adminurl="%s://%s/%s" % (u.scheme, u.netloc, self.config["path"])
response = requests.get(adminurl)
if response.status_code == 200:
result = self.result("Pass", "Django admin page is present at %s." % adminurl, response.content)
else:
result = self.result("Fail", "Default Django admin page is not present at %s" % adminurl, None)
return (result, response);


def configure(scanner):
if isinstance(scanner, Scanner) == False:
raise Exception("Cannot configure a non-scanner object!")
scanner.register_check(AdminAvailable())

0 comments on commit 61416f3

Please sign in to comment.