def test_preloaded(self): result = is_hsts_preloaded('bugzilla.mozilla.org') self.assertEquals('force-https', result['mode']) self.assertTrue(result['includeSubDomains']) result = is_hsts_preloaded('foo.bugzilla.mozilla.org') self.assertEquals('force-https', result['mode']) self.assertTrue(result['includeSubDomains']) result = is_hsts_preloaded('mail.yahoo.com') self.assertEqual('force-https', result['mode']) self.assertFalse(result['includeSubDomains'])
def test_preloaded(self): result = is_hsts_preloaded("bugzilla.mozilla.org") self.assertEquals("force-https", result["mode"]) self.assertTrue(result["includeSubDomains"]) result = is_hsts_preloaded("foo.bugzilla.mozilla.org") self.assertEquals("force-https", result["mode"]) self.assertTrue(result["includeSubDomains"]) result = is_hsts_preloaded("mail.yahoo.com") self.assertEqual("force-https", result["mode"]) self.assertFalse(result["includeSubDomains"]) # this domain is manually pinned result = is_hsts_preloaded("aus4.mozilla.org") self.assertTrue(result["pinned"]) self.assertTrue(result["includeSubDomainsForPinning"])
def test_preloaded(self): result = is_hsts_preloaded('bugzilla.mozilla.org') self.assertEquals('force-https', result['mode']) self.assertTrue(result['includeSubDomains']) result = is_hsts_preloaded('foo.bugzilla.mozilla.org') self.assertEquals('force-https', result['mode']) self.assertTrue(result['includeSubDomains']) result = is_hsts_preloaded('mail.yahoo.com') self.assertEqual('force-https', result['mode']) self.assertFalse(result['includeSubDomains']) # this domain is manually pinned result = is_hsts_preloaded('aus4.mozilla.org') self.assertTrue(result['pinned']) self.assertTrue(result['includeSubDomainsForPinning'])
def strict_transport_security( reqs: dict, expectation='hsts-implemented-max-age-at-least-six-months') -> dict: """ :param reqs: dictionary containing all the request and response objects :param expectation: test expectation hsts-implemented-max-age-at-least-six-months: HSTS implemented with a max age of at least six months (15768000) hsts-implemented-max-age-less-than-six-months: HSTS implemented with a max age of less than six months hsts-not-implemented-no-https: HSTS can't be implemented on http only sites hsts-not-implemented: HSTS not implemented hsts-header-invalid: HSTS header isn't parsable hsts-invalid-cert: Invalid certificate chain :return: dictionary with: data: the raw HSTS header expectation: test expectation includesubdomains: whether the includeSubDomains directive is set pass: whether the site's configuration met its expectation preload: whether the preload flag is set result: short string describing the result of the test """ SIX_MONTHS = 15552000 # 15768000 is six months, but a lot of sites use 15552000, so a white lie is in order output = { 'data': None, 'expectation': expectation, 'includeSubDomains': False, 'max-age': None, 'pass': False, 'preload': False, 'preloaded': False, 'result': 'hsts-not-implemented', } response = reqs['responses']['https'] # If there's no HTTPS, we can't have HSTS if response is None: output['result'] = 'hsts-not-implemented-no-https' # Also need a valid certificate chain for HSTS elif not response.verified: output['result'] = 'hsts-invalid-cert' elif 'Strict-Transport-Security' in response.headers: output['data'] = response.headers['Strict-Transport-Security'][ 0:1024] # code against malicious headers try: sts = [i.lower().strip() for i in output['data'].split(';')] # Throw an error if the header is set twice if ',' in output['data']: raise ValueError for parameter in sts: if parameter.startswith('max-age='): output['max-age'] = int(parameter[8:128]) # defense elif parameter == 'includesubdomains': output['includeSubDomains'] = True elif parameter == 'preload': output['preload'] = True if output['max-age']: if output[ 'max-age'] < SIX_MONTHS: # must be at least six months output[ 'result'] = 'hsts-implemented-max-age-less-than-six-months' else: output[ 'result'] = 'hsts-implemented-max-age-at-least-six-months' else: raise ValueError except: output['result'] = 'hsts-header-invalid' # If they're in the preloaded list, this overrides most anything else # TODO: Check to see if all redirect domains are preloaded # TODO: Check every redirect along the way for HSTS if response is not None: preloaded = is_hsts_preloaded(urlparse(response.url).netloc) if preloaded: output['result'] = 'hsts-preloaded' output['includeSubDomains'] = preloaded['includeSubDomains'] output['preloaded'] = True # Check to see if the test passed or failed if output['result'] in ('hsts-implemented-max-age-at-least-six-months', 'hsts-preloaded', expectation): output['pass'] = True return output
def test_not_preloaded(self): result = is_hsts_preloaded("totallyfakehostname.insertsuperduperfakedomainhere.wtftld") self.assertFalse(result)
def redirection(reqs: dict, expectation='redirection-to-https') -> dict: """ :param reqs: dictionary containing all the request and response objects :param expectation: test expectation redirection-to-https: Redirects from http to https, first redirection stays on host [default] redirection-not-to-https: Redirection takes place, but to another HTTP address redirection-not-to-https-on-initial-redirection: final destination HTTPS, but not initial redirection redirection-missing: No redirection takes place, staying on HTTP redirection-not-needed-no-http: Site doesn't listen for HTTP requests at all redirection-off-host-from-http: Initial HTTP allowed to go from one host to another, still redirects to HTTPS redirection-invalid-cert: Invalid certificate chain encountered :return: dictionary with: destination: final location of where GET / over HTTP ends expectation: test expectation pass: whether the site's configuration met its expectation path: the URLs that the requests followed to get to destination redirects: whether the site does any redirections at all result: short string describing the result of the test status-code: HTTP status code for the final redirection (typically 301 or 302) """ response = reqs['responses']['http'] output = { 'destination': response.url[0:2048] if response else None, # code defensively against long URLs 'expectation': expectation, 'pass': False, 'redirects': True, 'result': None, 'route': [], 'status_code': response.status_code if response else None, } if response is None: output['result'] = 'redirection-not-needed-no-http' # If we encountered an invalid certificate during the redirection process, that's a no-go elif not response.verified: output['result'] = 'redirection-invalid-cert' else: # Construct the route output['route'] = [r.request.url for r in response.history ] if response.history else [] output['route'] += [response.url] # Check to see if every redirection was covered by the preload list if all([ is_hsts_preloaded(url.netloc) for url in list(map(urlparse, output['route'])) ]): output['result'] = 'redirection-all-redirects-preloaded' # No redirection, so you just stayed on the http website elif len(output['route']) == 1: output['redirects'] = False output['result'] = 'redirection-missing' # Final destination wasn't an https website elif urlparse(output['route'][-1]).scheme != 'https': output['result'] = 'redirection-not-to-https' # http should never redirect to another http location -- should always go to https first elif urlparse(output['route'][1]).scheme == 'http': output[ 'result'] = 'redirection-not-to-https-on-initial-redirection' # If it's an http -> https redirection, make sure it redirects to the same host. If that's not done, then # HSTS cannot be properly set on the original host # TODO: Check for redirections like: http://www.example.com -> https://example.com -> https://www.example.com elif (urlparse(output['route'][0]).scheme == 'http' and urlparse(output['route'][1]).scheme == 'https' and urlparse(output['route'][0]).netloc.lower() != urlparse( output['route'][1]).netloc.lower()): output['result'] = 'redirection-off-host-from-http' output['status_code'] = response.history[-1].status_code else: output['result'] = 'redirection-to-https' # Code defensively against infinite routing loops and other shenanigans output['route'] = output['route'] if len(str( output['route'])) < 8192 else [] output['status_code'] = output['status_code'] if len( str(output['status_code'])) < 5 else None # Check to see if the test passed or failed if output['result'] in ('redirection-not-needed-no-http', 'redirection-all-redirects-preloaded', expectation): output['pass'] = True return output
def strict_transport_security(reqs: dict, expectation='hsts-implemented-max-age-at-least-six-months') -> dict: """ :param reqs: dictionary containing all the request and response objects :param expectation: test expectation hsts-implemented-max-age-at-least-six-months: HSTS implemented with a max age of at least six months (15768000) hsts-implemented-max-age-less-than-six-months: HSTS implemented with a max age of less than six months hsts-not-implemented-no-https: HSTS can't be implemented on http only sites hsts-not-implemented: HSTS not implemented hsts-header-invalid: HSTS header isn't parsable :return: dictionary with: data: the raw HSTS header expectation: test expectation includesubdomains: whether the includeSubDomains directive is set pass: whether the site's configuration met its expectation preload: whether the preload flag is set result: short string describing the result of the test """ SIX_MONTHS = 15552000 # 15768000 is six months, but a lot of sites use 15552000, so a white lie is in order output = { 'data': None, 'expectation': expectation, 'includeSubDomains': False, 'max-age': None, 'pass': False, 'preload': False, 'preloaded': False, 'result': 'hsts-not-implemented', } response = reqs['responses']['https'] # If there's no HTTPS, we can't have HSTS if response is None: output['result'] = 'hsts-not-implemented-no-https' elif 'Strict-Transport-Security' in response.headers: output['data'] = response.headers['Strict-Transport-Security'][0:1024] # code against malicious headers try: sts = [i.lower().strip() for i in output['data'].split(';')] # Throw an error if the header is set twice if ',' in output['data']: raise ValueError for parameter in sts: if parameter.startswith('max-age='): output['max-age'] = int(parameter[8:128]) # defense elif parameter == 'includesubdomains': output['includeSubDomains'] = True elif parameter == 'preload': output['preload'] = True if output['max-age']: if output['max-age'] < SIX_MONTHS: # must be at least six months output['result'] = 'hsts-implemented-max-age-less-than-six-months' else: output['result'] = 'hsts-implemented-max-age-at-least-six-months' else: raise ValueError except: output['result'] = 'hsts-header-invalid' # If they're in the preloaded list, this overrides most anything else if response is not None: preloaded = is_hsts_preloaded(urlparse(response.url).netloc) if preloaded: output['result'] = 'hsts-preloaded' output['includeSubDomains'] = preloaded['includeSubDomains'] output['preloaded'] = True # Check to see if the test passed or failed if output['result'] in ('hsts-implemented-max-age-at-least-six-months', 'hsts-preloaded', expectation): output['pass'] = True return output
def redirection(reqs: dict, expectation='redirection-to-https') -> dict: """ :param reqs: dictionary containing all the request and response objects :param expectation: test expectation redirection-to-https: Redirects from http to https, first redirection stays on host [default] redirection-not-to-https: Redirection takes place, but to another HTTP address redirection-not-to-https-on-initial-redirection: final destination HTTPS, but not initial redirection redirection-missing: No redirection takes place, staying on HTTP redirection-not-needed-no-http: Site doesn't listen for HTTP requests at all redirection-off-host-from-http: Initial HTTP allowed to go from one host to another, still redirects to HTTPS redirection-invalid-cert: Invalid certificate chain encountered :return: dictionary with: destination: final location of where GET / over HTTP ends expectation: test expectation pass: whether the site's configuration met its expectation path: the URLs that the requests followed to get to destination redirects: whether the site does any redirections at all result: short string describing the result of the test status-code: HTTP status code for the final redirection (typically 301 or 302) """ response = reqs['responses']['http'] output = { 'destination': response.url[0:2048] if response else None, # code defensively against long URLs 'expectation': expectation, 'pass': False, 'redirects': True, 'result': None, 'route': [], 'status_code': response.status_code if response else None, } if response is None: output['result'] = 'redirection-not-needed-no-http' # If we encountered an invalid certificate during the redirection process, that's a no-go elif not response.verified: output['result'] = 'redirection-invalid-cert' else: # Construct the route output['route'] = [r.request.url for r in response.history] if response.history else [] output['route'] += [response.url] # Check to see if every redirection was covered by the preload list if all([is_hsts_preloaded(url.netloc) for url in list(map(urlparse, output['route']))]): output['result'] = 'redirection-all-redirects-preloaded' # No redirection, so you just stayed on the http website elif len(output['route']) == 1: output['redirects'] = False output['result'] = 'redirection-missing' # Final destination wasn't an https website elif urlparse(output['route'][-1]).scheme != 'https': output['result'] = 'redirection-not-to-https' # http should never redirect to another http location -- should always go to https first elif urlparse(output['route'][1]).scheme == 'http': output['result'] = 'redirection-not-to-https-on-initial-redirection' # If it's an http -> https redirection, make sure it redirects to the same host. If that's not done, then # HSTS cannot be properly set on the original host # TODO: Check for redirections like: http://www.example.com -> https://example.com -> https://www.example.com elif (urlparse(output['route'][0]).scheme == 'http' and urlparse(output['route'][1]).scheme == 'https' and urlparse(output['route'][0]).netloc.lower() != urlparse(output['route'][1]).netloc.lower()): output['result'] = 'redirection-off-host-from-http' output['status_code'] = response.history[-1].status_code else: output['result'] = 'redirection-to-https' # Code defensively against infinite routing loops and other shenanigans output['route'] = output['route'] if len(str(output['route'])) < 8192 else [] output['status_code'] = output['status_code'] if len(str(output['status_code'])) < 5 else None # Check to see if the test passed or failed if output['result'] in ('redirection-not-needed-no-http', 'redirection-all-redirects-preloaded', expectation): output['pass'] = True return output
def test_not_preloaded(self): result = is_hsts_preloaded('totallyfakehostname.insertsuperduperfakedomainhere.wtftld') self.assertFalse(result)