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'])
    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"])
Example #4
0
    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'])
Example #5
0
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)
Example #7
0
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
Example #8
0
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
Example #9
0
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
Example #10
0
    def test_not_preloaded(self):
        result = is_hsts_preloaded('totallyfakehostname.insertsuperduperfakedomainhere.wtftld')

        self.assertFalse(result)