├── runtests.sh ├── MANIFEST.in ├── pip-selfcheck.json ├── pics ├── burp1.png ├── burp2.png ├── burp3.png ├── skipinfo.png ├── formatterCSV.png ├── generalusage.png ├── listcheckers.png ├── listformatters.png ├── multiplesites.png ├── multiplecheckers.png ├── skipheadermissing.png └── multiplesitesflattened.png ├── securityheaders ├── models │ ├── server │ │ ├── __init__.py │ │ └── server.py │ ├── hpkp │ │ ├── __init__.py │ │ └── hpkp.py │ ├── xpoweredby │ │ ├── __init__.py │ │ └── xpoweredby.py │ ├── xwebkitcsp │ │ ├── __init__.py │ │ └── xwebkitcsp.py │ ├── xaspnetversion │ │ ├── __init__.py │ │ └── xaspnetversion.py │ ├── securityheader.py │ ├── xaspnetmvcversion │ │ ├── __init__.py │ │ └── xaspnetmvcversion.py │ ├── xcsp │ │ ├── __init__.py │ │ └── xcontentxecuritypolicy.py │ ├── hsts │ │ ├── __init__.py │ │ ├── hstsdirective.py │ │ └── hsts.py │ ├── expectct │ │ ├── __init__.py │ │ ├── expectctdirective.py │ │ └── expectct.py │ ├── setcookie │ │ ├── __init__.py │ │ └── setcookiedirective.py │ ├── clearsitedata │ │ ├── __init__.py │ │ ├── csd.py │ │ └── csddirective.py │ ├── xdownloadoptions │ │ ├── __init__.py │ │ ├── xdo.py │ │ └── xdodirective.py │ ├── xframeoptions │ │ ├── __init__.py │ │ ├── xframeoptionsdirective.py │ │ └── xframeoptions.py │ ├── referrerpolicy │ │ ├── __init__.py │ │ ├── referrerpolicydirective.py │ │ └── referrerpolicy.py │ ├── cors │ │ ├── maxage │ │ │ ├── __init__.py │ │ │ ├── accesscontrolmaxagedirective.py │ │ │ └── accesscontrolmaxage.py │ │ ├── __init__.py │ │ ├── alloworigin │ │ │ ├── __init__.py │ │ │ ├── accesscontrolalloworigindirective.py │ │ │ └── accesscontrolalloworigin.py │ │ ├── allowheaders │ │ │ ├── __init__.py │ │ │ ├── accesscontrolallowheadersdirective.py │ │ │ └── accesscontrolallowheaders.py │ │ ├── allowmethods │ │ │ ├── __init__.py │ │ │ ├── accesscontrolallowmethods.py │ │ │ └── accesscontrolallowmethodsdirective.py │ │ ├── exposeheaders │ │ │ ├── __init__.py │ │ │ ├── accesscontrolexposeheadersdirective.py │ │ │ └── accesscontrolexposeheaders.py │ │ ├── allowcredentials │ │ │ ├── __init__.py │ │ │ ├── accesscontrolallowcredentialsdirective.py │ │ │ └── accesscontrolallowcredentials.py │ │ └── corsdirective.py │ ├── xpcdp │ │ ├── __init__.py │ │ ├── xpcdpdirective.py │ │ └── xpcdp.py │ ├── xcontenttypeoptions │ │ ├── __init__.py │ │ ├── xcontenttypeoptionsdirective.py │ │ └── xcontenttypeoptions.py │ ├── featurepolicy │ │ ├── __init__.py │ │ ├── featurepolicykeyword.py │ │ ├── test_featurepolicy.py │ │ ├── featurepolicydirective.py │ │ └── featurepolicy.py │ ├── xxssprotection │ │ ├── __init__.py │ │ ├── xxssprotectionkeyword.py │ │ ├── xxssprotectiondirective.py │ │ └── xxssprotection.py │ ├── csp │ │ ├── __init__.py │ │ ├── cspkeyword.py │ │ ├── cspreportonly.py │ │ ├── cspversion.py │ │ └── cspdirective.py │ ├── keyword.py │ ├── annotations.py │ ├── directive.py │ ├── __init__.py │ └── modelfactory.py ├── checkers │ ├── csp │ │ ├── cspcheck.py │ │ ├── wildcard.py │ │ ├── checkerro.py │ │ ├── wildcard_ro.py │ │ ├── srchttp.py │ │ ├── deprecateddirective.py │ │ ├── frameancestors.py │ │ ├── srchttp_ro.py │ │ ├── deprecateddirective_ro.py │ │ ├── ipsourcechecker.py │ │ ├── noncelength.py │ │ ├── frameancestors_ro.py │ │ ├── ipsourcechecker_ro.py │ │ ├── missingdirective.py │ │ ├── noncelength_ro.py │ │ ├── missingdirective_ro.py │ │ ├── plainurlschemes.py │ │ ├── plainurlschemes_ro.py │ │ ├── unsafeeval.py │ │ ├── unsafeinline.py │ │ ├── unsafeeval_ro.py │ │ ├── unsafeinline_ro.py │ │ ├── whitelistbypasscdn.py │ │ ├── whitelistbypass.py │ │ ├── whitelistbypasscdn_ro.py │ │ ├── flashobjectivewhitelistbypass.py │ │ ├── whitelistbypass_ro.py │ │ ├── flashobjectivewhitelistbypass_ro.py │ │ ├── cspcheck_deprecated.py │ │ ├── roonlychecker.py │ │ ├── cspcheck_unsafeeval.py │ │ ├── cspcheck_unsafeinline.py │ │ ├── cspcheck_plainurlschemes.py │ │ ├── cspcheck_wildcard.py │ │ ├── cspcheck_srchttp.py │ │ ├── cspcheck_frameancestors.py │ │ ├── cspcheck_whitelistcdn.py │ │ ├── cspxframeopts.py │ │ ├── cspcheck_ipsource.py │ │ ├── test_deprecateddirective.py │ │ ├── test_srchttp.py │ │ ├── cspcheck_noncelength.py │ │ ├── cspcheck_flash.py │ │ ├── checker.py │ │ ├── test_plainurlschemes.py │ │ ├── test_noncelength.py │ │ ├── test_ipsourcechecker.py │ │ ├── test_frameancestors.py │ │ └── cspcheck_missingdirective.py │ ├── server │ │ ├── __init__.py │ │ └── present.py │ ├── syntaxchecker.py │ ├── xpoweredby │ │ ├── __init__.py │ │ ├── present.py │ │ └── test_present.py │ ├── cors │ │ ├── allowcredentials │ │ │ ├── __init__.py │ │ │ └── checker.py │ │ ├── maxage │ │ │ ├── __init__.py │ │ │ ├── checker.py │ │ │ ├── toolong.py │ │ │ └── test_toolong.py │ │ ├── exposeheaders │ │ │ ├── __init__.py │ │ │ ├── checker.py │ │ │ ├── exposesensitiveheaders.py │ │ │ └── test_exposesensitiveheaders.py │ │ ├── alloworigin │ │ │ ├── checker.py │ │ │ ├── __init__.py │ │ │ ├── star.py │ │ │ ├── null.py │ │ │ ├── httpcreds.py │ │ │ ├── test_null.py │ │ │ ├── test_star.py │ │ │ └── test_httpcreds.py │ │ └── __init__.py │ ├── infocollector.py │ ├── featurepolicy │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── test_wildcard.py │ │ └── wildcard.py │ ├── xpcdp │ │ ├── __init__.py │ │ ├── checker.py │ │ └── notnone.py │ ├── referrerpolicy │ │ ├── __init__.py │ │ ├── checker.py │ │ └── insecure.py │ ├── xframeoptions │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── good.py │ │ └── test_good.py │ ├── xcontenttypeoptions │ │ ├── __init__.py │ │ ├── checker.py │ │ └── nosniff.py │ ├── hsts │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── subdomains.py │ │ ├── maxagezero.py │ │ ├── test_maxagezero.py │ │ └── test_subdomains.py │ ├── setcookie │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── requiressecurity.py │ │ ├── notsecure.py │ │ └── nothttponly.py │ ├── expectct │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── httpreporturi.py │ │ ├── test_httpreporturi.py │ │ ├── notenforce.py │ │ └── test_notenforce.py │ ├── xxssprotection │ │ ├── __init__.py │ │ ├── checker.py │ │ ├── block.py │ │ ├── httpsreport.py │ │ ├── test_block.py │ │ └── test_httpsreport.py │ ├── other │ │ ├── xwebkitcspdeprecated.py │ │ ├── hpkpdeprecated.py │ │ ├── xcspdeprecated.py │ │ ├── __init__.py │ │ ├── xasp.py │ │ ├── test_xwebkitcspdeprecated.py │ │ ├── test_xcspdeprecated.py │ │ └── test_xasp.py │ ├── infodisclosure.py │ ├── infoheadercollector.py │ ├── headerpresentchecker.py │ ├── headerdeprecatedchecker.py │ ├── findingseverity.py │ ├── checker.py │ ├── headeremptychecker.py │ ├── headermissingchecker.py │ ├── headerevaluator.py │ ├── infourlcollector.py │ ├── infodirectivecollector.py │ ├── directivemissingchecker.py │ ├── directiveemptychecker.py │ ├── unknowndirectivechecker.py │ ├── finding.py │ ├── findingtype.py │ ├── missingseparatorchecker.py │ └── __init__.py ├── formatters │ └── __init__.py ├── singleton.py ├── __init__.py └── test_util.py ├── conf ├── flashbypasses.txt ├── jsonpwhitelistbypassneedseval.txt ├── app.conf └── cdn.txt ├── requirements.txt ├── top10.txt ├── Dockerfile ├── securityheaders.py ├── setup.py ├── top10.csv └── .gitignore /runtests.sh: -------------------------------------------------------------------------------- 1 | python -m unittest discover 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include conf * 2 | 3 | -------------------------------------------------------------------------------- /pip-selfcheck.json: -------------------------------------------------------------------------------- 1 | {"last_check":"2018-07-31T08:29:12Z","pypi_version":"18.0"} -------------------------------------------------------------------------------- /pics/burp1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/burp1.png -------------------------------------------------------------------------------- /pics/burp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/burp2.png -------------------------------------------------------------------------------- /pics/burp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/burp3.png -------------------------------------------------------------------------------- /pics/skipinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/skipinfo.png -------------------------------------------------------------------------------- /securityheaders/models/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .server import Server 2 | 3 | __all__ = ['Server'] 4 | -------------------------------------------------------------------------------- /pics/formatterCSV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/formatterCSV.png -------------------------------------------------------------------------------- /pics/generalusage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/generalusage.png -------------------------------------------------------------------------------- /pics/listcheckers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/listcheckers.png -------------------------------------------------------------------------------- /pics/listformatters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/listformatters.png -------------------------------------------------------------------------------- /pics/multiplesites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/multiplesites.png -------------------------------------------------------------------------------- /pics/multiplecheckers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/multiplecheckers.png -------------------------------------------------------------------------------- /securityheaders/models/hpkp/__init__.py: -------------------------------------------------------------------------------- 1 | from .hpkp import PublicKeyPins 2 | 3 | __all__ = ['PublicKeyPins'] 4 | -------------------------------------------------------------------------------- /pics/skipheadermissing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/skipheadermissing.png -------------------------------------------------------------------------------- /securityheaders/models/xpoweredby/__init__.py: -------------------------------------------------------------------------------- 1 | from .xpoweredby import XPoweredBy 2 | 3 | __all__ = ['XPoweredBy'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/xwebkitcsp/__init__.py: -------------------------------------------------------------------------------- 1 | from .xwebkitcsp import XWebKitCSP 2 | 3 | __all__ = ['XWebKitCSP'] 4 | -------------------------------------------------------------------------------- /conf/flashbypasses.txt: -------------------------------------------------------------------------------- 1 | //vk.com/swf/video.swf 2 | //ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf 3 | -------------------------------------------------------------------------------- /pics/multiplesitesflattened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koenbuyens/securityheaders/HEAD/pics/multiplesitesflattened.png -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck.py: -------------------------------------------------------------------------------- 1 | class CSPCheck(object): 2 | def check(self, opt_options=dict()): 3 | pass 4 | -------------------------------------------------------------------------------- /securityheaders/checkers/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .present import ServerPresentChecker 2 | 3 | __all__ = ['ServerPresentChecker'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/xaspnetversion/__init__.py: -------------------------------------------------------------------------------- 1 | from .xaspnetversion import XAspnetVersion 2 | 3 | __all__ = ['XAspnetVersion'] 4 | -------------------------------------------------------------------------------- /securityheaders/checkers/syntaxchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | 3 | class SyntaxChecker(Checker): 4 | pass 5 | -------------------------------------------------------------------------------- /securityheaders/models/securityheader.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | 3 | class SecurityHeader(Header): 4 | 5 | pass 6 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpoweredby/__init__.py: -------------------------------------------------------------------------------- 1 | from .present import XPoweredByPresentChecker 2 | 3 | __all__ = ['XPoweredByPresentChecker'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/xaspnetmvcversion/__init__.py: -------------------------------------------------------------------------------- 1 | from .xaspnetmvcversion import XAspnetMvcVersion 2 | 3 | __all__ = ['XAspnetMvcVersion'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/xcsp/__init__.py: -------------------------------------------------------------------------------- 1 | from .xcontentxecuritypolicy import XContentSecurityPolicy 2 | 3 | __all__ = ['XContentSecurityPolicy'] 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anytree>=2.4.3,<3 2 | argcomplete>=1.9.4,<2 3 | enum34>=1.1.6,<2 4 | ipaddress>=1.0.22,<2 5 | six>=1.11.0,<2 6 | tabulate>=0.8.2,<1 7 | -------------------------------------------------------------------------------- /securityheaders/models/hsts/__init__.py: -------------------------------------------------------------------------------- 1 | from .hstsdirective import HSTSDirective 2 | from .hsts import HSTS 3 | 4 | __all__ = ['HSTSDirective','HSTS'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/allowcredentials/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import AccessControlAllowCredentialsChecker 2 | 3 | __all__ = ['AccessControlAllowCredentialsChecker'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/expectct/__init__.py: -------------------------------------------------------------------------------- 1 | from .expectctdirective import ExpectCTDirective 2 | from .expectct import ExpectCT 3 | 4 | __all__ = ['ExpectCTDirective','ExpectCT'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/setcookie/__init__.py: -------------------------------------------------------------------------------- 1 | from .setcookiedirective import SetCookieDirective 2 | from .setcookie import SetCookie 3 | 4 | __all__ = ['SetCookieDirective','SetCookie'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/clearsitedata/__init__.py: -------------------------------------------------------------------------------- 1 | from .csddirective import ClearSiteDataDirective 2 | from .csd import ClearSiteData 3 | 4 | __all__ = ['ClearSiteDataDirective','ClearSiteData'] 5 | -------------------------------------------------------------------------------- /top10.txt: -------------------------------------------------------------------------------- 1 | google.com 2 | facebook.com 3 | youtube.com 4 | twitter.com 5 | microsoft.com 6 | linkedin.com 7 | wikipedia.org 8 | plus.google.com 9 | instagram.com 10 | apple.com 11 | 12 | -------------------------------------------------------------------------------- /conf/jsonpwhitelistbypassneedseval.txt: -------------------------------------------------------------------------------- 1 | googletagmanager.com 2 | www.googletagmanager.com 3 | www.googleadservices.com 4 | google-analytics.com 5 | ssl.google-analytics.com 6 | www.google-analytics.com 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/infocollector.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | 3 | class InfoCollector(Checker): 4 | 5 | def check(self, headers, opt_options=dict()): 6 | pass 7 | -------------------------------------------------------------------------------- /securityheaders/models/xdownloadoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .xdodirective import XDownloadOptionsDirective 2 | from .xdo import XDownloadOptions 3 | 4 | __all__ = ['XDownloadOptionsDirective','XDownloadOptions'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/xframeoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .xframeoptionsdirective import XFrameOptionsDirective 2 | from .xframeoptions import XFrameOptions 3 | 4 | __all__ = ['XFrameOptionsDirective','XFrameOptions'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/featurepolicy/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import FeaturePolicyChecker 2 | from .wildcard import FeaturePolicyWildCardChecker 3 | 4 | __all__ = ['FeaturePolicyChecker', 'FeaturePolicyWildCardChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpcdp/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import XPermittedCrossDomainPolicyChecker 2 | from .notnone import XPCDPNotNoneChecker 3 | 4 | __all__ = ['XPermittedCrossDomainPolicyChecker','XPCDPNotNoneChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/referrerpolicy/__init__.py: -------------------------------------------------------------------------------- 1 | from .referrerpolicydirective import ReferrerPolicyDirective 2 | from .referrerpolicy import ReferrerPolicy 3 | 4 | __all__ = ['ReferrerPolicyDirective','ReferrerPolicy'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/referrerpolicy/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import ReferrerPolicyChecker 2 | from .insecure import ReferrerPolicyInsecureChecker 3 | 4 | __all__ = ['ReferrerPolicyChecker','ReferrerPolicyInsecureChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/xframeoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import XFrameOptionsChecker 2 | from .good import XFrameOptionsNotAllowFromChecker 3 | 4 | __all__ = ['XFrameOptionsChecker', 'XFrameOptionsNotAllowFromChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/maxage/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import AccessControlMaxAgeChecker 2 | from .toolong import AccessControlMaxAgeTooLongChecker 3 | 4 | __all__ = ['AccessControlMaxAgeChecker','AccessControlMaxAgeTooLongChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/xcontenttypeoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import XContentTypeOptionsChecker 2 | from .nosniff import XContentTypeOptionsNoSniffChecker 3 | 4 | __all__ = ['XContentTypeOptionsChecker','XContentTypeOptionsNoSniffChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/cors/maxage/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolmaxage import AccessControlMaxAge 2 | from .accesscontrolmaxagedirective import AccessControlMaxAgeDirective 3 | 4 | __all__ = ['AccessControlMaxAge','AccessControlMaxAgeDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/xpcdp/__init__.py: -------------------------------------------------------------------------------- 1 | from .xpcdpdirective import XPermittedCrossDomainPoliciesDirective 2 | from .xpcdp import XPermittedCrossDomainPolicies 3 | 4 | __all__ = ['XPermittedCrossDomainPoliciesDirective','XPermittedCrossDomainPolicies'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import HSTSChecker 2 | from .maxagezero import HSTSMaxAgeZeroChecker 3 | from .subdomains import HSTSSubdomainsChecker 4 | 5 | __all__ = ['HSTSChecker','HSTSMaxAgeZeroChecker','HSTSSubdomainsChecker'] 6 | -------------------------------------------------------------------------------- /securityheaders/models/xcontenttypeoptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .xcontenttypeoptionsdirective import XContentTypeOptionsDirective 2 | from .xcontenttypeoptions import XContentTypeOptions 3 | 4 | __all__ = ['XContentTypeOptionsDirective','XContentTypeOptions'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/cors/__init__.py: -------------------------------------------------------------------------------- 1 | from .corsdirective import CORSDirective 2 | from .allowcredentials import * 3 | from .allowheaders import * 4 | from .allowmethods import * 5 | from .alloworigin import * 6 | from .exposeheaders import * 7 | from .maxage import * 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2-alpine 2 | LABEL maintainer="Koen Buyens" 3 | 4 | COPY . /app 5 | WORKDIR /app 6 | 7 | # install dependencies 8 | RUN pip install --no-cache-dir -r requirements.txt 9 | 10 | # run securityheaders 11 | ENTRYPOINT ["python", "securityheaders.py"] 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/setcookie/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import SetCookieChecker 2 | from .notsecure import CookieNotSecureChecker 3 | from .nothttponly import CookieNotHttpOnlyChecker 4 | 5 | __all__ = ['SetCookieChecker','CookieNotSecureChecker', 'CookieNotHttpOnlyChecker'] 6 | -------------------------------------------------------------------------------- /securityheaders/formatters/__init__.py: -------------------------------------------------------------------------------- 1 | from .findingformatterfactory import FindingFormatterFactory, FindingFormatter, FindingFormatterTabulated, FindingFormatterCSV 2 | 3 | __all__ = ['FindingFormatter','FindingFormatterFactory','FindingFormatterTabulated','FindingFormatterCSV'] 4 | -------------------------------------------------------------------------------- /securityheaders/models/cors/alloworigin/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolalloworigin import AccessControlAllowOrigin 2 | from .accesscontrolalloworigindirective import AccessControlAllowOriginDirective 3 | 4 | __all__ = ['AccessControlAllowOrigin','AccessControlAllowOriginDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowheaders/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolallowheaders import AccessControlAllowHeaders 2 | from .accesscontrolallowheadersdirective import AccessControlAllowHeadersDirective 3 | 4 | __all__ = ['AccessControlAllowHeaders','AccessControlAllowHeadersDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowmethods/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolallowmethods import AccessControlAllowMethods 2 | from .accesscontrolallowmethodsdirective import AccessControlAllowMethodsDirective 3 | 4 | __all__ = ['AccessControlAllowMethods','AccessControlAllowMethodsDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/exposeheaders/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import AccessControlExposeHeadersChecker 2 | from .exposesensitiveheaders import AccessControlExposeHeadersSensitiveChecker 3 | 4 | __all__ = ['AccessControlExposeHeadersChecker','AccessControlExposeHeadersSensitiveChecker'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/wildcard.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_wildcard import CSPCheckWildCard 3 | 4 | 5 | class CSPWildCardChecker(CSPChecker): 6 | def check(self, headers, opt_options=dict()): 7 | return CSPCheckWildCard(self.getcsp(headers)).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import ExpectCTChecker 2 | from .httpreporturi import ExpectCTHTTPReportURIChecker 3 | from .notenforce import ExpectCTNotEnforcedChecker 4 | 5 | __all__ = ['ExpectCTChecker','ExpectCTHTTPReportURIChecker','ExpectCTNotEnforcedChecker'] 6 | -------------------------------------------------------------------------------- /securityheaders/models/cors/exposeheaders/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolexposeheaders import AccessControlExposeHeaders 2 | from .accesscontrolexposeheadersdirective import AccessControlExposeHeadersDirective 3 | 4 | __all__ = ['AccessControlExposeHeaders','AccessControlExposeHeadersDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/models/featurepolicy/__init__.py: -------------------------------------------------------------------------------- 1 | from .featurepolicydirective import FeaturePolicyDirective 2 | from .featurepolicykeyword import FeaturePolicyKeyword 3 | from .featurepolicy import FeaturePolicy 4 | 5 | __all__ = ['FeaturePolicy', 'FeaturePolicyDirective','FeaturePolicyKeyword'] 6 | -------------------------------------------------------------------------------- /securityheaders.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # PYTHON_ARGCOMPLETE_OK 3 | import sys 4 | 5 | from multiprocessing import freeze_support 6 | import securityheaders.command_line 7 | 8 | if __name__== "__main__": 9 | freeze_support() 10 | securityheaders.command_line.main(sys.argv[1:]) 11 | -------------------------------------------------------------------------------- /securityheaders/models/xxssprotection/__init__.py: -------------------------------------------------------------------------------- 1 | from .xxssprotectiondirective import XXSSProtectionDirective 2 | from .xxssprotectionkeyword import XXSSProtectionKeyword 3 | from .xxssprotection import XXSSProtection 4 | 5 | __all__ = ['XXSSProtectionDirective', 'XXSSProtectionKeyword','XXSSProtection'] 6 | -------------------------------------------------------------------------------- /securityheaders/models/csp/__init__.py: -------------------------------------------------------------------------------- 1 | from .cspdirective import CSPDirective 2 | from .cspkeyword import CSPKeyword 3 | from .cspversion import CSPVersion 4 | from .csp import CSP 5 | from .cspreportonly import CSPReportOnly 6 | 7 | __all__ = ['CSPDirective', 'CSPKeyword', 'CSPVersion', 'CSP','CSPReportOnly'] 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import XXSSProtectionChecker 2 | from .block import XXSSProtectionBlockChecker 3 | from .httpsreport import XXSSProtectionHTTPSReportChecker 4 | 5 | __all__ = ['XXSSProtectionChecker', 'XXSSProtectionBlockChecker','XXSSProtectionHTTPSReportChecker'] 6 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowcredentials/__init__.py: -------------------------------------------------------------------------------- 1 | from .accesscontrolallowcredentials import AccessControlAllowCredentials 2 | from .accesscontrolallowcredentialsdirective import AccessControlAllowCredentialsDirective 3 | 4 | __all__ = ['AccessControlAllowCredentials','AccessControlAllowCredentialsDirective'] 5 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.hsts import HSTS 2 | from securityheaders.checkers import Checker 3 | 4 | class HSTSChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def gethsts(self, headers): 9 | return self.extractheader(headers, HSTS) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/checkerro.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers.csp import CSPChecker 2 | from securityheaders.models.csp import CSPReportOnly, CSPDirective, CSPVersion 3 | 4 | class CSPReportOnlyChecker(CSPChecker): 5 | 6 | def getcsp(self, headers): 7 | return self.extractheader(headers, CSPReportOnly) 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/wildcard_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_wildcard import CSPCheckWildCard 3 | 4 | class CSPReportOnlyWildCardChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | return CSPCheckWildCard(self.getcsp(headers)).check() 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.expectct import ExpectCT 2 | from securityheaders.checkers import Checker 3 | 4 | class ExpectCTChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getexpectct(self, headers): 9 | return self.extractheader(headers, ExpectCT) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/setcookie/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.setcookie import SetCookie 2 | from securityheaders.checkers import Checker 3 | 4 | class SetCookieChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getcookie(self, headers): 9 | return self.extractheader(headers, SetCookie) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/srchttp.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_srchttp import CSPCheckSrcHttp 3 | 4 | class CSPSCRHTTPChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | return CSPCheckSrcHttp(self.getcsp(headers), self.applyCheckFunktionToDirectives).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/xwebkitcspdeprecated.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import HeaderDeprecatedChecker 2 | from securityheaders.models import XWebKitCSP 3 | 4 | class XWebKitCSPDeprecatedChecker(HeaderDeprecatedChecker): 5 | def check(self, headers, options=[]): 6 | return HeaderDeprecatedChecker.mycheck(self, headers, XWebKitCSP.headerkey) 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/deprecateddirective.py: -------------------------------------------------------------------------------- 1 | #checks whether csp of v3 contains report-uri 2 | from .checker import CSPChecker 3 | from .cspcheck_deprecated import CSPCheckDeprecated 4 | 5 | class CSPDeprecatedDirectiveChecker(CSPChecker): 6 | def check(self, headers, opt_options=dict()): 7 | return CSPCheckDeprecated(self.getcsp(headers)).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/infodisclosure.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import HeaderPresentChecker 2 | 3 | class InfoDisclosureChecker(HeaderPresentChecker): 4 | def mycheck(self, header, headers, opt_options=dict()): 5 | return HeaderPresentChecker.mycheck(self, headers, header, 'This header gives an attacker info for more targeted attacks.', opt_options) 6 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/hpkpdeprecated.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import HeaderDeprecatedChecker 2 | from securityheaders.models import PublicKeyPins 3 | 4 | class PublicKeyPinsDeprecatedChecker(HeaderDeprecatedChecker): 5 | def check(self, headers, options=[]): 6 | return HeaderDeprecatedChecker.mycheck(self, headers, PublicKeyPins.headerkey) 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/server/present.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import InfoDisclosureChecker 2 | from securityheaders.models import Server 3 | 4 | class ServerPresentChecker(InfoDisclosureChecker): 5 | def check(self, headers, opt_options=dict()): 6 | return InfoDisclosureChecker.mycheck(self, Server.headerkey,headers,opt_options) 7 | 8 | 9 | -------------------------------------------------------------------------------- /securityheaders/checkers/xframeoptions/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xframeoptions import XFrameOptions 2 | from securityheaders.checkers import Checker 3 | 4 | class XFrameOptionsChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getxframeoptions(self, headers): 9 | return self.extractheader(headers, XFrameOptions) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | from securityheaders.models.xxssprotection import XXSSProtection 3 | 4 | class XXSSProtectionChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getxxss(self, headers): 9 | return self.extractheader(headers, XXSSProtection) 10 | 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/xcspdeprecated.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import HeaderDeprecatedChecker 2 | from securityheaders.models import XContentSecurityPolicy 3 | 4 | class XCSPDeprecatedChecker(HeaderDeprecatedChecker): 5 | def check(self, headers, options=[]): 6 | return HeaderDeprecatedChecker.mycheck(self, headers, XContentSecurityPolicy.headerkey) 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/referrerpolicy/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.referrerpolicy import ReferrerPolicy 2 | from securityheaders.checkers import Checker 3 | 4 | class ReferrerPolicyChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getreferrerpolicy(self, headers): 9 | return self.extractheader(headers, ReferrerPolicy) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/maxage/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlMaxAge 2 | from securityheaders.checkers import Checker 3 | 4 | class AccessControlMaxAgeChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getmaxage(self, headers): 9 | return self.extractheader(headers, AccessControlMaxAge) 10 | 11 | 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpoweredby/present.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import InfoDisclosureChecker 2 | from securityheaders.models import XPoweredBy 3 | 4 | class XPoweredByPresentChecker(InfoDisclosureChecker): 5 | def check(self, headers, opt_options=dict()): 6 | return InfoDisclosureChecker.mycheck(self, XPoweredBy.headerkey,headers, opt_options) 7 | 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/frameancestors.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_frameancestors import CSPCheckFrameAncestors 3 | 4 | class CSPFrameAncestorsChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | return CSPCheckFrameAncestors(self.getcsp(headers), self.applyCheckFunktionToDirectives).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/srchttp_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_srchttp import CSPCheckSrcHttp 3 | 4 | class CSPReportOnlySCRHTTPChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | return CSPCheckSrcHttp(self.getcsp(headers), self.applyCheckFunktionToDirectives).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/__init__.py: -------------------------------------------------------------------------------- 1 | from .xcspdeprecated import XCSPDeprecatedChecker 2 | from .xwebkitcspdeprecated import XWebKitCSPDeprecatedChecker 3 | from .xasp import XASPNetPresentChecker 4 | from .hpkpdeprecated import PublicKeyPinsDeprecatedChecker 5 | 6 | __all__ = ['XCSPDeprecatedChecker','XWebKitCSPDeprecatedChecker','XASPNetPresentChecker', 'PublicKeyPinsDeprecatedChecker'] 7 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpcdp/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xpcdp import XPermittedCrossDomainPolicies 2 | from securityheaders.checkers import Checker 3 | 4 | class XPermittedCrossDomainPolicyChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getxpcdp(self, headers): 9 | return self.extractheader(headers, XPermittedCrossDomainPolicies) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlAllowOrigin 2 | from securityheaders.checkers import Checker 3 | 4 | class AccessControlAllowOriginChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getorigins(self, headers): 9 | return self.extractheader(headers, AccessControlAllowOrigin) 10 | 11 | 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/deprecateddirective_ro.py: -------------------------------------------------------------------------------- 1 | #checks whether csp of v3 contains report-uri 2 | from .checkerro import CSPReportOnlyChecker 3 | from .cspcheck_deprecated import CSPCheckDeprecated 4 | 5 | class CSPReportOnlyDeprecatedDirectiveChecker(CSPReportOnlyChecker): 6 | def check(self, headers, opt_options=dict()): 7 | return CSPCheckDeprecated(self.getcsp(headers)).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/ipsourcechecker.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_ipsource import CSPCheckIPSource 3 | 4 | class CSPIPSourceChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckIPSource(csp,self.applyCheckFunktionToDirectives).check() 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/xcontenttypeoptions/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xcontenttypeoptions import XContentTypeOptions 2 | 3 | from securityheaders.checkers import Checker 4 | 5 | class XContentTypeOptionsChecker(Checker): 6 | def __init__(self): 7 | pass 8 | 9 | def getxcontenttypeoptions(self, headers): 10 | return self.extractheader(headers, XContentTypeOptions) 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/exposeheaders/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlExposeHeaders 2 | from securityheaders.checkers import Checker 3 | 4 | class AccessControlExposeHeadersChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getexposeheaders(self, headers): 9 | return self.extractheader(headers, AccessControlExposeHeaders) 10 | 11 | 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/noncelength.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_noncelength import CSPCheckNonceLength 3 | 4 | class CSPNonceLengthChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckNonceLength(csp,self.applyCheckFunktionToDirectives).check() 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/frameancestors_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_frameancestors import CSPCheckFrameAncestors 3 | 4 | class CSPReportOnlyFrameAncestorsChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | return CSPCheckFrameAncestors(self.getcsp(headers), self.applyCheckFunktionToDirectives).check() 8 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/allowcredentials/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | from securityheaders.models.cors.allowcredentials import AccessControlAllowCredentials 3 | 4 | class AccessControlAllowCredentialsChecker(Checker): 5 | def __init__(self): 6 | pass 7 | 8 | def getallowcreds(self, headers): 9 | return self.extractheader(headers, AccessControlAllowCredentials) 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/ipsourcechecker_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_ipsource import CSPCheckIPSource 3 | 4 | class CSPReportOnlyIPSourceChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckIPSource(csp,self.applyCheckFunktionToDirectives).check() 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/missingdirective.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_missingdirective import CSPCheckMissingDirective 3 | 4 | class CSPMissingDirectiveChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckMissingDirective(csp,self.applyCheckFunktionToDirectives).check() 10 | 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/setcookie/requiressecurity.py: -------------------------------------------------------------------------------- 1 | def requires_security(cookie): 2 | name = cookie.name() 3 | if not name: 4 | return False 5 | name = name.lower() 6 | if name.startswith('__Secure'): 7 | return True 8 | if name.startswith('__Host'): 9 | return True 10 | if 'session' in name: 11 | return True 12 | if 'csrf' in name: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import AccessControlAllowOriginChecker 2 | from .httpcreds import AccessControlAllowOriginHTTPCredsChecker 3 | from .null import AccessControlAllowOriginNullChecker 4 | from .star import AccessControlAllowOriginStarChecker 5 | 6 | __all__ = ['AccessControlAllowOriginChecker','AccessControlAllowOriginHTTPCredsChecker','AccessControlAllowOriginNullChecker','AccessControlAllowOriginStarChecker'] 7 | -------------------------------------------------------------------------------- /securityheaders/singleton.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class _Singleton(type): 4 | """ A metaclass that creates a Singleton base class when called. """ 5 | _instances = {} 6 | def __call__(cls, *args, **kwargs): 7 | if cls not in cls._instances: 8 | cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs) 9 | return cls._instances[cls] 10 | 11 | class Singleton(_Singleton('SingletonMeta', (object,), {})): pass 12 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | [Checker] 2 | enabled=True 3 | 4 | [CSPChecker] 5 | CSPVersion=3 6 | 7 | [CSPFlashObjectWhitelistBypassChecker] 8 | bypasses=file://conf/flashbypasses.txt 9 | 10 | [CSPScriptWhitelistBypassChecker] 11 | angular=file://conf/angularwhitelistbypasses.txt 12 | jsonp=file://conf/jsonpwhitelistbypasses.txt 13 | jsonpeval=file://conf/jsonpwhitelistbypassneedseval.txt 14 | 15 | [CSPScriptWhitelistCDNBypassChecker] 16 | cdn=file://conf/cdn.txt 17 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/noncelength_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_noncelength import CSPCheckNonceLength 3 | 4 | class CSPReportOnlyNonceLengthChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckNonceLength(csp,self.applyCheckFunktionToDirectives).check(opt_options) 10 | -------------------------------------------------------------------------------- /securityheaders/models/xxssprotection/xxssprotectionkeyword.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Keyword 2 | 3 | class XXSSProtectionKeyword(Keyword): 4 | BLOCK = "block" 5 | 6 | @staticmethod 7 | def isKeyword(keyword): 8 | """ Checks whether a given string is a XSSProtection keyword. 9 | 10 | Args: 11 | keyword (str): the string to validate 12 | """ 13 | return hasattr(XSSProtectionKeyword, keyword) 14 | -------------------------------------------------------------------------------- /securityheaders/models/xwebkitcsp/xwebkitcsp.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | from securityheaders.models.annotations import * 3 | 4 | @description('Deprecated header for a Content-Security-Policy.') 5 | @headername('x-webkit-csp') 6 | @headerref('http://www.w3.org/TR/CSP/') 7 | class XWebKitCSP(Header): 8 | directive = None 9 | 10 | def __init__(self, unparsedstring): 11 | self.parsedstring = unparsedstring 12 | self.parsed = False 13 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/missingdirective_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_missingdirective import CSPCheckMissingDirective 3 | 4 | class CSPReportOnlyMissingDirectiveChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | return CSPCheckMissingDirective(csp,self.applyCheckFunktionToDirectives).check() 10 | 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/plainurlschemes.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_plainurlschemes import CSPCheckPlainUrlSchemes 3 | 4 | class CSPPlainUrlSchemesChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | values = csp.getEffectiveDirectives(csp.DIRECTIVES_CAUSING_XSS) 10 | return CSPCheckPlainUrlSchemes(csp,values).check() 11 | -------------------------------------------------------------------------------- /securityheaders/models/keyword.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class Keyword(Enum): 4 | 5 | def __str__(self): 6 | """ Returns a string representaiton of this CSP keyword 7 | """ 8 | return str(self.value.lower()) 9 | 10 | def lower(self): 11 | return str(self).lower() 12 | 13 | def startswith(self, value): 14 | return str(self).startswith(value) 15 | 16 | def find(self, value): 17 | return str(self).find(value) 18 | -------------------------------------------------------------------------------- /securityheaders/models/xaspnetversion/xaspnetversion.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | from securityheaders.models.annotations import * 3 | 4 | @description('Header describing the server software.') 5 | @headername('x-aspnet-version') 6 | @headerref('https://msdn.microsoft.com/en-us/library/cc224063.aspx') 7 | class XAspnetVersion(Header): 8 | directive = None 9 | def __init__(self, unparsedstring): 10 | self.parsedstring = unparsedstring 11 | self.parsed = False 12 | -------------------------------------------------------------------------------- /securityheaders/models/server/server.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | from securityheaders.models.annotations import description, headername, headerref 3 | 4 | @description('Header describing the server software.') 5 | @headername('server') 6 | @headerref('https://tools.ietf.org/html/rfc7231#section-7.4.2') 7 | class Server(Header): 8 | 9 | directive = None 10 | 11 | def __init__(self, unparsedstring): 12 | self.parsedstring = unparsedstring 13 | self.parsed = False 14 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/plainurlschemes_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_plainurlschemes import CSPCheckPlainUrlSchemes 3 | 4 | class CSPReportOnlyPlainUrlSchemesChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | values = csp.getEffectiveDirectives(csp.DIRECTIVES_CAUSING_XSS) 10 | return CSPCheckPlainUrlSchemes(csp,values).check() 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/infoheadercollector.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import InfoCollector, FindingType, Finding, FindingSeverity 2 | 3 | class InfoHeaderCollector(InfoCollector): 4 | 5 | def check(self, headers, opt_options=dict()): 6 | if not headers: 7 | return [] 8 | findings = [] 9 | for header in headers.keys(): 10 | findings.append(Finding(header, FindingType.INFO_HEADER, headers[header],FindingSeverity.NONE, None, None)) 11 | return findings 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/unsafeeval.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_unsafeeval import CSPCheckUnsafeEval 3 | 4 | class CSPUnsafeEvalChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | directive = csp.directive.SCRIPT_SRC 10 | values = self.effectiveDirectiveValues(headers,directive, opt_options) 11 | return CSPCheckUnsafeEval(csp,directive, values).check() 12 | -------------------------------------------------------------------------------- /securityheaders/models/cors/maxage/accesscontrolmaxagedirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | 3 | class AccessControlMaxAgeDirective(CORSDirective): 4 | @classmethod 5 | def isDirective(cls, directive): 6 | """ Checks whether a given string is a directive 7 | 8 | Args: 9 | directive (str): the string to validate 10 | """ 11 | try: 12 | int(directive) 13 | return True 14 | except: 15 | return False 16 | -------------------------------------------------------------------------------- /securityheaders/models/xcsp/xcontentxecuritypolicy.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | 4 | @description('Deprecated header for a Content-Security-Policy.') 5 | @headername('x-content-security-policy') 6 | @headerref('http://www.w3.org/TR/CSP/') 7 | class XContentSecurityPolicy(SecurityHeader): 8 | directive = None 9 | 10 | def __init__(self, unparsedstring): 11 | self.parsedstring = unparsedstring 12 | self.parsed = False 13 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/xasp.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import InfoDisclosureChecker 2 | 3 | from securityheaders.models import XAspnetVersion, XAspnetMvcVersion 4 | 5 | class XASPNetPresentChecker(InfoDisclosureChecker): 6 | def check(self, headers, opt_options=dict()): 7 | result = InfoDisclosureChecker.mycheck(self, XAspnetVersion.headerkey,headers, opt_options) 8 | result.extend(InfoDisclosureChecker.mycheck(self,XAspnetMvcVersion.headerkey,headers, opt_options)) 9 | return result 10 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/unsafeinline.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_unsafeinline import CSPCheckUnsafeInline 3 | 4 | class CSPUnsafeInlineChecker(CSPChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | directive = csp.directive.SCRIPT_SRC 10 | values = self.effectiveDirectiveValues(headers,directive, opt_options) 11 | return CSPCheckUnsafeInline(csp,directive, values).check() 12 | 13 | -------------------------------------------------------------------------------- /securityheaders/checkers/headerpresentchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker, FindingType, Finding, FindingSeverity 2 | 3 | class HeaderPresentChecker(Checker): 4 | def mycheck(self, headers, header, description, options): 5 | if not header or not headers: 6 | return [] 7 | 8 | if header in headers.keys(): 9 | return [Finding(header, FindingType.INFO_DISCLOSURE, header + ' header present. ' + description,FindingSeverity.INFO, None, headers[header])] 10 | return [] 11 | -------------------------------------------------------------------------------- /securityheaders/models/cors/exposeheaders/accesscontrolexposeheadersdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | from securityheaders.models.annotations import * 3 | 4 | @anydirective 5 | class AccessControlExposeHeadersDirective(CORSDirective): 6 | @classmethod 7 | def isDirective(cls, directive): 8 | """ Checks whether a given string is a directive 9 | 10 | Args: 11 | directive (str): the string to validate 12 | """ 13 | return isinstance(directive, str) 14 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/unsafeeval_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_unsafeeval import CSPCheckUnsafeEval 3 | 4 | class CSPReportOnlyUnsafeEvalChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | directive = csp.directive.SCRIPT_SRC 10 | values = self.effectiveDirectiveValues(headers,directive, opt_options) 11 | return CSPCheckUnsafeEval(csp,directive, values).check() 12 | -------------------------------------------------------------------------------- /securityheaders/models/xpoweredby/xpoweredby.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | from securityheaders.models.annotations import * 3 | 4 | @description('Header describing the server software.') 5 | @headername('x-powered-by') 6 | @headerref('https://stackoverflow.com/questions/1288338/why-does-asp-net-framework-add-the-x-powered-byasp-net-http-header-in-respons') 7 | class XPoweredBy(Header): 8 | directive = None 9 | 10 | def __init__(self, unparsedstring): 11 | self.parsedstring = unparsedstring 12 | self.parsed = False 13 | -------------------------------------------------------------------------------- /securityheaders/models/xaspnetmvcversion/xaspnetmvcversion.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Header 2 | from securityheaders.models.annotations import * 3 | 4 | @description('Header describing the server software.') 5 | @headername('x-aspnetmvc-version') 6 | @headerref('https://support.microsoft.com/en-us/help/4295294/update-rollup-13-for-windows-azure-pack') 7 | class XAspnetMvcVersion(Header): 8 | 9 | directive = None 10 | 11 | def __init__(self, unparsedstring): 12 | self.parsedstring = unparsedstring 13 | self.parsed = False 14 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/unsafeinline_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_unsafeinline import CSPCheckUnsafeInline 3 | 4 | class CSPReportOnlyUnsafeInlineChecker(CSPReportOnlyChecker): 5 | def check(self, headers, opt_options=dict()): 6 | csp = self.getcsp(headers) 7 | if not csp: 8 | return [] 9 | directive = csp.directive.SCRIPT_SRC 10 | values = self.effectiveDirectiveValues(headers,directive, opt_options) 11 | return CSPCheckUnsafeInline(csp,directive, values).check() 12 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/whitelistbypasscdn.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_whitelistcdn import CSPCheckWhitelistCDN 3 | 4 | class CSPScriptWhitelistCDNBypassChecker(CSPChecker): 5 | def myoptions(cls): 6 | return {'cdn':list} 7 | 8 | def check(self, headers, opt_options=dict()): 9 | me = self.__class__.__name__ 10 | if me in opt_options.keys(): 11 | options = opt_options[me] 12 | else: 13 | options = {} 14 | return CSPCheckWhitelistCDN(self.getcsp(headers)).check(options) 15 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/whitelistbypass.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_whitelist import CSPCheckWhitelist 3 | 4 | class CSPScriptWhitelistBypassChecker(CSPChecker): 5 | def myoptions(cls): 6 | return {'angular':list, 'jsonp':list, 'jsonpeval':list} 7 | 8 | def check(self, headers, opt_options=dict()): 9 | me = self.__class__.__name__ 10 | if me in opt_options.keys(): 11 | options = opt_options[me] 12 | else: 13 | options = {} 14 | return CSPCheckWhitelist(self.getcsp(headers)).check(options) 15 | -------------------------------------------------------------------------------- /securityheaders/models/xcontenttypeoptions/xcontenttypeoptionsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | class XContentTypeOptionsDirective(Directive): 4 | NOSNIFF = 'nosniff' 5 | 6 | 7 | @classmethod 8 | def isDirective(cls, directive): 9 | """ Checks whether a given string is a directive 10 | 11 | Args: 12 | directive (str): the string to validate 13 | """ 14 | if isinstance(directive, XContentTypeOptionsDirective): 15 | return True 16 | return any(directive.lower() == item.value.lower() for item in cls) 17 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/whitelistbypasscdn_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_whitelistcdn import CSPCheckWhitelistCDN 3 | 4 | class CSPReportOnlyScriptWhitelistCDNBypassChecker(CSPReportOnlyChecker): 5 | def myoptions(cls): 6 | return {'cdn':list} 7 | 8 | def check(self, headers, opt_options=dict()): 9 | me = self.__class__.__name__ 10 | if me in opt_options.keys(): 11 | options = opt_options[me] 12 | else: 13 | options = {} 14 | return CSPCheckWhitelistCDN(self.getcsp(headers)).check(options) 15 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/flashobjectivewhitelistbypass.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .cspcheck_flash import CSPCheckFlash 3 | 4 | class CSPFlashObjectWhitelistBypassChecker(CSPChecker): 5 | 6 | def myoptions(cls): 7 | return {'bypasses':list} 8 | 9 | def check(self, headers, opt_options=dict()): 10 | csp = self.getcsp(headers) 11 | me = self.__class__.__name__ 12 | if me in opt_options.keys(): 13 | options = opt_options[me] 14 | else: 15 | options = {} 16 | return CSPCheckFlash(self.getcsp(headers)).check(options) 17 | -------------------------------------------------------------------------------- /securityheaders/__init__.py: -------------------------------------------------------------------------------- 1 | from .util import Util 2 | 3 | from . import checkers 4 | from . import models 5 | from . import singleton 6 | 7 | import pkgutil 8 | import inspect 9 | 10 | 11 | from .securityheader import SecurityHeaders 12 | from securityheaders.checkers import FindingSeverity, CheckerFactory 13 | from securityheaders.models import * 14 | from securityheaders.formatters import * 15 | 16 | __all__ = ['util', 'models','checkers', 'Singleton', 'SecurityHeaders', 'FindingSeverity', 'CheckerFactory'] 17 | __all__.extend(ModelFactory().getnames()) 18 | __all__.extend(FindingFormatterFactory().getnames()) 19 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowheaders/accesscontrolallowheadersdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | from securityheaders.models.annotations import * 3 | 4 | @anydirective 5 | class AccessControlAllowHeadersDirective(CORSDirective): 6 | 7 | @classmethod 8 | def isDirective(cls, directive): 9 | """ Checks whether a given string is a directive 10 | 11 | Args: 12 | directive (str): the string to validate 13 | """ 14 | return isinstance(directive, str) 15 | 16 | @classmethod 17 | def directiveseperator(cls): 18 | return ',' 19 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/whitelistbypass_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_whitelist import CSPCheckWhitelist 3 | 4 | class CSPReportOnlyScriptWhitelistBypassChecker(CSPReportOnlyChecker): 5 | def myoptions(cls): 6 | return {'angular':list, 'jsonp':list, 'jsonpeval':list} 7 | 8 | def check(self, headers, opt_options=dict()): 9 | me = self.__class__.__name__ 10 | if me in opt_options.keys(): 11 | options = opt_options[me] 12 | else: 13 | options = {} 14 | return CSPCheckWhitelist(self.getcsp(headers)).check(options) 15 | -------------------------------------------------------------------------------- /securityheaders/models/featurepolicy/featurepolicykeyword.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Keyword 2 | 3 | class FeaturePolicyKeyword(Keyword): 4 | SELF = "'self'" 5 | NONE = "'none'" 6 | STAR = "*" 7 | 8 | 9 | @staticmethod 10 | def isKeyword(keyword): 11 | """ Checks whether a given string is a FeaturePolicyKeyword. 12 | 13 | Args: 14 | keyword (str): the string to validate 15 | """ 16 | return hasattr(FeaturePolicyKeyword, keyword) 17 | 18 | @staticmethod 19 | def isValue(keyword): 20 | return keyword in list(map(str, FeaturePolicyKeyword)) 21 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/flashobjectivewhitelistbypass_ro.py: -------------------------------------------------------------------------------- 1 | from .checkerro import CSPReportOnlyChecker 2 | from .cspcheck_flash import CSPCheckFlash 3 | 4 | class CSPReportOnlyFlashObjectWhitelistBypassChecker(CSPReportOnlyChecker): 5 | 6 | def myoptions(cls): 7 | return {'bypasses':list} 8 | 9 | def check(self, headers, opt_options=dict()): 10 | csp = self.getcsp(headers) 11 | me = self.__class__.__name__ 12 | if me in opt_options.keys(): 13 | options = opt_options[me] 14 | else: 15 | options = {} 16 | return CSPCheckFlash(self.getcsp(headers)).check(options) 17 | -------------------------------------------------------------------------------- /securityheaders/checkers/headerdeprecatedchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker, FindingType, Finding, FindingSeverity 2 | 3 | class HeaderDeprecatedChecker(Checker): 4 | 5 | def mycheck(self, headers, header): 6 | if not header or not headers: 7 | return [] 8 | 9 | if header in headers.keys() or header.lower() in headers.keys(): 10 | value = headers[header] if header in headers.keys() else headers[header.lower()] 11 | return [Finding(header, FindingType.DEPRECATED_HEADER, header + ' header present. This header is deprecated and should not be used.',FindingSeverity.INFO, None, value)] 12 | return [] 13 | -------------------------------------------------------------------------------- /securityheaders/models/csp/cspkeyword.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Keyword 2 | 3 | class CSPKeyword(Keyword): 4 | SELF = "'self'" 5 | NONE = "'none'" 6 | UNSAFE_INLINE = "'unsafe-inline'" 7 | UNSAFE_EVAL = "'unsafe-eval'" 8 | STRICT_DYNAMIC = "'strict-dynamic'" 9 | STAR = "*" 10 | 11 | 12 | @staticmethod 13 | def isKeyword(keyword): 14 | """ Checks whether a given string is a CSP keyword. 15 | 16 | Args: 17 | keyword (str): the string to validate 18 | """ 19 | return hasattr(CSPKeyword, keyword) 20 | 21 | @staticmethod 22 | def isValue(keyword): 23 | return keyword in list(map(str, CSPKeyword)) 24 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/__init__.py: -------------------------------------------------------------------------------- 1 | from .allowcredentials import * 2 | from .alloworigin import * 3 | from .maxage import * 4 | from .exposeheaders import * 5 | 6 | import pkgutil 7 | import inspect 8 | 9 | __all__ = [] 10 | 11 | for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): 12 | if "test" not in module_name: 13 | module = loader.find_module(module_name).load_module(module_name) 14 | for name, obj in inspect.getmembers(module): 15 | if hasattr(obj, "__name__") and ("checker" in obj.__name__.lower() or "collector" in obj.__name__.lower()) and obj.__name__ not in __all__ and inspect.isclass(obj): 16 | __all__.append(obj.__name__) 17 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/subdomains.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.hsts import HSTS 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.hsts import HSTSChecker 4 | 5 | class HSTSSubdomainsChecker(HSTSChecker): 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | hsts = self.gethsts(headers) 9 | 10 | if not hsts: 11 | return findings 12 | if not hsts.includesubdomains(): 13 | return [Finding(HSTS.headerkey, FindingType.NO_SUBDOMAINS, 'include subdomains was not specified.', FindingSeverity.LOW, HSTS.directive.INCLUDESUBDOMAINS)] 14 | 15 | return findings 16 | -------------------------------------------------------------------------------- /securityheaders/models/cors/corsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | class CORSDirective(Directive): 4 | 5 | 6 | @classmethod 7 | def directiveseperator(cls): 8 | return ',' 9 | 10 | @classmethod 11 | def directivevalueseperator(cls): 12 | return ',' 13 | 14 | 15 | @classmethod 16 | def isDirective(cls, directive): 17 | """ Checks whether a given string is a directive 18 | 19 | Args: 20 | directive (str): the string to validate 21 | """ 22 | if isinstance(directive, CORSDirective): 23 | return True 24 | return any(directive.lower() == item.value.lower() for item in list(cls)) 25 | -------------------------------------------------------------------------------- /securityheaders/models/hpkp/hpkp.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | 4 | @description('HTTP Public Key Pinning (HPKP) is a trust on first use security mechanism which protects HTTPS websites from impersonation using fraudulent certificates issued by compromised certificate authorities. The security context or pinset data is supplied by the site or origin.') 5 | @headername('public-key-pins') 6 | @headerref('https://tools.ietf.org/html/rfc7469') 7 | class PublicKeyPins(SecurityHeader): 8 | directive = None 9 | 10 | def __init__(self, unparsedstring): 11 | self.parsedstring = unparsedstring 12 | self.parsed = False 13 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/maxagezero.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.hsts import HSTS 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.hsts import HSTSChecker 4 | 5 | class HSTSMaxAgeZeroChecker(HSTSChecker): 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | hsts = self.gethsts(headers) 9 | 10 | if not hsts: 11 | return findings 12 | if hsts.maxAge() == 0: 13 | return [Finding(HSTS.headerkey, FindingType.MAX_AGE_ZERO, str(HSTS.headerkey) + ' is disabled due to max age equal to zero.', FindingSeverity.LOW, HSTS.directive.MAX_AGE)] 14 | 15 | return findings 16 | 17 | -------------------------------------------------------------------------------- /securityheaders/models/clearsitedata/csd.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.clearsitedata import ClearSiteDataDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @description('Clearing browser data for origin.') 6 | @headername('clear-site-data') 7 | @headerref('https://w3c.github.io/webappsec-clear-site-data/') 8 | class ClearSiteData(SecurityHeader): 9 | directive = ClearSiteDataDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, ClearSiteData.directive) 13 | 14 | def methods(self): 15 | if self.parsedstring: 16 | return self.keys() 17 | return [] 18 | 19 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowcredentials/accesscontrolallowcredentialsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | from securityheaders.models.annotations import requireddirectives 3 | 4 | @requireddirectives('true') 5 | class AccessControlAllowCredentialsDirective(CORSDirective): 6 | TRUE= 'true' 7 | 8 | @classmethod 9 | def isDirective(cls, directive): 10 | """ Checks whether a given string is a directive 11 | 12 | Args: 13 | directive (str): the string to validate 14 | """ 15 | if isinstance(directive, AccessControlAllowCredentialsDirective): 16 | return True 17 | return any(directive.lower() == item.value.lower() for item in list(cls)) 18 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/star.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlAllowOriginDirective, AccessControlAllowOrigin 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from .checker import AccessControlAllowOriginChecker 4 | 5 | class AccessControlAllowOriginStarChecker(AccessControlAllowOriginChecker): 6 | def check(self, headers, opt_options=dict()): 7 | origins = self.getorigins(headers) 8 | if origins and origins.isstar(): 9 | return [Finding(AccessControlAllowOrigin.headerkey, FindingType.STAR_ORIGIN, str(AccessControlAllowOrigin.headerkey) + " should not be *",FindingSeverity.HIGH, AccessControlAllowOriginDirective.STAR, None)] 10 | return [] 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/null.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlAllowOriginDirective, AccessControlAllowOrigin 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from .checker import AccessControlAllowOriginChecker 4 | 5 | class AccessControlAllowOriginNullChecker(AccessControlAllowOriginChecker): 6 | def check(self, headers, opt_options=dict()): 7 | origins = self.getorigins(headers) 8 | if origins and origins.isnull(): 9 | return [Finding(AccessControlAllowOrigin.headerkey, FindingType.NULL_ORIGIN, str(AccessControlAllowOrigin.headerkey) + " should not be *",FindingSeverity.HIGH, AccessControlAllowOriginDirective.NULL, None)] 10 | return [] 11 | 12 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowheaders/accesscontrolallowheaders.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolallowheadersdirective import AccessControlAllowHeadersDirective 4 | 5 | @description('TODO') 6 | @headername('access-control-allow-headers') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlAllowHeaders(SecurityHeader): 9 | directive = AccessControlAllowHeadersDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlAllowHeadersDirective) 13 | 14 | def headers(self): 15 | if self.parsedstring: 16 | return self.keys() 17 | return [] 18 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowmethods/accesscontrolallowmethods.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolallowmethodsdirective import AccessControlAllowMethodsDirective 4 | 5 | @description('TODO') 6 | @headername('access-control-allow-methods') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlAllowMethods(SecurityHeader): 9 | directive = AccessControlAllowMethodsDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlAllowMethodsDirective) 13 | 14 | def methods(self): 15 | if self.parsedstring: 16 | return self.keys() 17 | return [] 18 | -------------------------------------------------------------------------------- /securityheaders/models/cors/alloworigin/accesscontrolalloworigindirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | 3 | class AccessControlAllowOriginDirective(CORSDirective): 4 | STAR ='*' 5 | NULL = 'null' 6 | 7 | @classmethod 8 | def isDirective(cls, directive): 9 | """ Checks whether a given string is a directive 10 | 11 | Args: 12 | directive (str): the string to validate 13 | """ 14 | if isinstance(directive, AccessControlAllowOriginDirective): 15 | return True 16 | if directive.startswith('http:') or directive.startswith('https'): 17 | return True 18 | 19 | return any(directive.lower() == item.value.lower() for item in list(cls)) 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='advancedsecurityheaders', 4 | version='0.3', 5 | description='Python script to check for incorrectly configured security headers.', 6 | url='http://github.com/koenbuyens/securityheaders', 7 | author='Koen Buyens', 8 | author_email='koen@buyens.org', 9 | license='MIT', 10 | packages=find_packages(), 11 | zip_safe=False, 12 | entry_points = { 13 | 'console_scripts': ['advancedsecurityheaders=securityheaders.command_line:main'], 14 | }, 15 | install_requires=[ 16 | 'enum34', 17 | 'ipaddress', 18 | 'tabulate', 19 | 'anytree', 20 | 'argcomplete', 21 | 'six' 22 | ]) 23 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_deprecated.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from .cspcheck import CSPCheck 3 | 4 | class CSPCheckDeprecated(CSPCheck): 5 | 6 | def __init__(self, csp): 7 | self.csp = csp 8 | 9 | def check(self): 10 | csp = self.csp 11 | if not csp or not csp.parsedstring: 12 | return [] 13 | 14 | findings = [] 15 | if csp.directive.REPORT_URI in csp.parsedstring: 16 | findings.append(Finding(csp.headerkey,FindingType.DEPRECATED_DIRECTIVE,'report-uri is deprecated in CSP3. Please use the report-to directive instead.', FindingSeverity.INFO, csp.directive.REPORT_URI)) 17 | return findings 18 | return [] 19 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/roonlychecker.py: -------------------------------------------------------------------------------- 1 | from .checker import CSPChecker 2 | from .checkerro import CSPReportOnlyChecker 3 | from securityheaders.checkers import Finding,FindingType,FindingSeverity 4 | 5 | class CSPReportOnlyNoCSPChecker(CSPReportOnlyChecker, CSPChecker): 6 | def check(self, headers, opt_options=dict()): 7 | rocsp = CSPReportOnlyChecker.getcsp(self,headers) 8 | csp = CSPChecker.getcsp(self,headers) 9 | 10 | if not csp and rocsp: 11 | description = "The CSP is not enforced as only the content-security-policy-report-only header is present. Can you set the content-security-policy?" 12 | return [Finding(rocsp.headerkey, FindingType.REPORT_ONLY,description,FindingSeverity.INFO, None, None)] 13 | return [] 14 | -------------------------------------------------------------------------------- /securityheaders/models/xframeoptions/xframeoptionsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | from securityheaders.models.annotations import requireddirectives, requireddirectivevalues 4 | 5 | @requireddirectivevalues('allow-from') 6 | class XFrameOptionsDirective(Directive): 7 | DENY = 'deny' 8 | SAMEORIGIN = 'sameorigin' 9 | ALLOW_FROM = 'allow-from' 10 | 11 | @classmethod 12 | def isDirective(cls, directive): 13 | """ Checks whether a given string is a directive 14 | 15 | Args: 16 | directive (str): the string to validate 17 | """ 18 | if isinstance(directive, XFrameOptionsDirective): 19 | return True 20 | return any(directive.lower() == item.value.lower() for item in cls) 21 | -------------------------------------------------------------------------------- /securityheaders/models/cors/maxage/accesscontrolmaxage.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolmaxagedirective import AccessControlMaxAgeDirective 4 | 5 | @description('TODO') 6 | @headername('access-control-max-age') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlMaxAge(SecurityHeader): 9 | directive = AccessControlMaxAgeDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlMaxAgeDirective) 13 | 14 | 15 | def maxage(self): 16 | if self.parsedstring and len(self.parsedstring) > 0: 17 | result = self.keys()[0] 18 | return int(result) 19 | return None 20 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/httpreporturi.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | 3 | from .checker import ExpectCTChecker 4 | 5 | class ExpectCTHTTPReportURIChecker(ExpectCTChecker): 6 | 7 | def check(self, headers, opt_options=dict()): 8 | findings = [] 9 | expectct = self.getexpectct(headers) 10 | 11 | if not expectct: 12 | return findings 13 | 14 | findings = [] 15 | if expectct.reporturi() and expectct.reporturi().startswith('http://'): 16 | findings.append(Finding(expectct.headerkey,FindingType.SRC_HTTP,expectct.headerkey + 'communicates its reports via an insecure channel.', FindingSeverity.LOW, expectct.reporturi())) 17 | return findings 18 | -------------------------------------------------------------------------------- /securityheaders/models/featurepolicy/test_featurepolicy.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.featurepolicy import FeaturePolicy, FeaturePolicyKeyword, FeaturePolicyDirective 2 | import unittest 3 | 4 | class FeaturePolicyTest(unittest.TestCase): 5 | def setUp(self): 6 | self.fpgeolocation = FeaturePolicy("geolocation 'none'") 7 | self.fpcomplex = FeaturePolicy("unsized-media 'none'; geolocation 'self' https://example.com; camera *;") 8 | 9 | def test_parsing(self): 10 | geo = self.fpcomplex.getEffectiveValues(FeaturePolicyDirective.GEOLOCATION) 11 | self.assertTrue(FeaturePolicyKeyword.SELF in geo) 12 | self.assertTrue("https://example.com" in geo) 13 | self.assertEqual(len(geo), 2) 14 | 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /securityheaders/checkers/setcookie/notsecure.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.setcookie import SetCookie 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.setcookie import SetCookieChecker 4 | 5 | from .requiressecurity import requires_security 6 | 7 | class CookieNotSecureChecker(SetCookieChecker): 8 | def check(self, headers, opt_options=dict()): 9 | cookie = self.getcookie(headers) 10 | 11 | if not cookie: 12 | return [] 13 | if not cookie.secure() and requires_security(cookie): 14 | return [Finding(SetCookie.headerkey, FindingType.INSECURE_HEADER, 'The cookie does not have the secure flag set.', FindingSeverity.MEDIUM,SetCookie.directive.SECURE, None)] 15 | return [] 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /securityheaders/checkers/setcookie/nothttponly.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.setcookie import SetCookie 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.setcookie import SetCookieChecker 4 | from .requiressecurity import requires_security 5 | 6 | class CookieNotHttpOnlyChecker(SetCookieChecker): 7 | def check(self, headers, opt_options=dict()): 8 | cookie = self.getcookie(headers) 9 | 10 | if not cookie: 11 | return [] 12 | 13 | if not cookie.httponly() and requires_security(cookie): 14 | return [Finding(SetCookie.headerkey, FindingType.INSECURE_HEADER, 'The cookie does not have the httponly flag set.', FindingSeverity.MEDIUM,SetCookie.directive.HTTPONLY, None)] 15 | return [] 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowcredentials/accesscontrolallowcredentials.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolallowcredentialsdirective import AccessControlAllowCredentialsDirective 4 | 5 | @description('TODO') 6 | @headername('access-control-allow-credentials') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlAllowCredentials(SecurityHeader): 9 | directive = AccessControlAllowCredentialsDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlAllowCredentialsDirective) 13 | 14 | 15 | def value(self): 16 | if self.parsedstring and len(self.parsedstring) > 0: 17 | return self.keys()[0] 18 | return None 19 | -------------------------------------------------------------------------------- /securityheaders/models/csp/cspreportonly.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import re 3 | import copy 4 | 5 | from securityheaders.models import SecurityHeader 6 | from securityheaders.models.csp import CSPDirective, CSPKeyword, CSPVersion, CSP 7 | from securityheaders.models.annotations import description, headername 8 | 9 | @description('This header tests the CSP header. The CSP header protects against XSS attacks. By whitelisting sources of approved content, the browser does not load malicious assets.') 10 | @headername('content-security-policy-report-only') 11 | class CSPReportOnly(CSP): 12 | directive = CSPDirective 13 | keyword = CSPKeyword 14 | 15 | 16 | def __init__(self, unparsedstring): 17 | self.__class__.required = False 18 | SecurityHeader.__init__(self, unparsedstring, CSPDirective, CSPKeyword) 19 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/block.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xxssprotection import XXSSProtectionDirective, XXSSProtection 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.xxssprotection import XXSSProtectionChecker 4 | 5 | class XXSSProtectionBlockChecker(XXSSProtectionChecker): 6 | def check(self, headers, opt_options=dict()): 7 | xxss = self.getxxss(headers) 8 | if xxss and not xxss.one(): 9 | return [Finding(XXSSProtection.headerkey, FindingType.DISABLE_XSS_FILTER,'This header sets the configuration for the cross-site scripting filter built into most browsers. The recommended value is "X-XSS-Protection: 1; mode=block".',FindingSeverity.LOW, XXSSProtectionDirective.ONE,XXSSProtectionDirective.ZERO)] 10 | return [] 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/findingseverity.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class FindingSeverity(Enum): 4 | ERROR = 2000 5 | CRITICAL = 1000 #Critical severity 6 | HIGH = 900 #high severity 7 | SYNTAX = 800 #syntax error 8 | HIGH_MAYBE = 750 #high severity, but needs to be confirmed by user 9 | MEDIUM = 700 #medium severity 10 | MEDIUM_MAYBE = 650 #medium severity, but needs to be confired by user 11 | STRICT_CSP = 600 #the CSP does not adhere to strict guidelines 12 | LOW = 500 #low severity 13 | INFO = 400 #informational severity 14 | NONE = 100 #no severity 15 | 16 | 17 | 18 | def __str__(self): 19 | """ Returns a string representaiton of this finding severity 20 | """ 21 | return str(self.name) 22 | 23 | def __repr__(self): 24 | return self.__str__() 25 | 26 | -------------------------------------------------------------------------------- /securityheaders/checkers/checker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from securityheaders import Util 4 | 5 | 6 | class Checker(object): 7 | 8 | def options(cls): 9 | result = cls.myoptions() 10 | for base in cls.__class__.__bases__: 11 | if hasattr(base, 'options'): 12 | opts = base.options(base()) 13 | result.update(opts) 14 | return result 15 | 16 | def myoptions(cls): 17 | return {} 18 | 19 | def check(self, tocheck, opt_options=[]): 20 | pass 21 | 22 | def extractheader(self, headers, headerobject): 23 | if not headers: 24 | return None 25 | headerkey = headerobject.headerkey 26 | if headerkey in headers.keys(): 27 | return headerobject(headers[headerkey]) 28 | else: 29 | return None 30 | -------------------------------------------------------------------------------- /securityheaders/models/xdownloadoptions/xdo.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.xdownloadoptions import XDownloadOptionsDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @description('Prevent file downloads opening.') 6 | @headername('x-download-options') 7 | @headerref('https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/jj542450(v=vs.85)') 8 | class XDownloadOptions(SecurityHeader): 9 | directive = XDownloadOptionsDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, XDownloadOptions.directive) 13 | 14 | def noopen(self): 15 | if self.parsedstring: 16 | return XDownloadOptionsDirective.NOOPEN in self.keys() 17 | return [] 18 | 19 | -------------------------------------------------------------------------------- /securityheaders/models/xxssprotection/xxssprotectiondirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import requireddirectives, requireddirectivevalues 3 | 4 | @requireddirectivevalues('mode','report') 5 | class XXSSProtectionDirective(Directive): 6 | 7 | ONE = '1' 8 | ZERO = '0' 9 | REPORT = 'report' 10 | MODE = 'mode' 11 | 12 | @classmethod 13 | def valueseperator(cls): 14 | return '=' 15 | 16 | @classmethod 17 | def isDirective(cls, directive): 18 | """ Checks whether a given string is a directive 19 | 20 | Args: 21 | directive (str): the string to validate 22 | """ 23 | if isinstance(directive, XXSSProtectionDirective): 24 | return True 25 | return any(directive.lower() == item.value.lower() for item in cls) 26 | -------------------------------------------------------------------------------- /top10.csv: -------------------------------------------------------------------------------- 1 | GlobalRank,TldRank,Domain,TLD,RefSubNets,RefIPs,IDN_Domain,IDN_TLD,PrevGlobalRank,PrevTldRank,PrevRefSubNets,PrevRefIPs 2 | 1,1,google.com,com,469841,3006056,google.com,com,1,1,470197,3021955 3 | 2,2,facebook.com,com,456392,3052087,facebook.com,com,2,2,456832,3069603 4 | 3,3,youtube.com,com,415722,2462185,youtube.com,com,3,3,416072,2479139 5 | 4,4,twitter.com,com,413907,2551414,twitter.com,com,4,4,414287,2566563 6 | 5,5,microsoft.com,com,309048,1183023,microsoft.com,com,5,5,309628,1186678 7 | 6,6,linkedin.com,com,297357,1359114,linkedin.com,com,6,6,297855,1367342 8 | 7,1,wikipedia.org,org,290977,1257665,wikipedia.org,org,7,1,291338,1265290 9 | 8,7,plus.google.com,com,287437,1463868,plus.google.com,com,8,7,287559,1471687 10 | 9,8,instagram.com,com,282291,1387201,instagram.com,com,9,8,282435,1394292 11 | 10,9,apple.com,com,280746,1071939,apple.com,com,10,9,281145,1078204 12 | -------------------------------------------------------------------------------- /securityheaders/models/cors/allowmethods/accesscontrolallowmethodsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import CORSDirective 2 | 3 | class AccessControlAllowMethodsDirective(CORSDirective): 4 | GET = 'get' 5 | HEAD = 'head' 6 | POST = 'post' 7 | PUT = 'put' 8 | DELETE = 'delete' 9 | CONNECT = 'connect' 10 | OPTIONS = 'options' 11 | TRACE = 'trace' 12 | PATCH = 'patch' 13 | 14 | @classmethod 15 | def isDirective(cls, directive): 16 | 17 | """ Checks whether a given string is a directive 18 | 19 | Args: 20 | directive (str): the string to validate 21 | """ 22 | if isinstance(directive, AccessControlAllowMethodsDirective): 23 | result = True 24 | else: 25 | result= any(directive.lower() == item.value.lower() for item in list(cls)) 26 | return result 27 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/httpsreport.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xxssprotection import XXSSProtectionDirective, XXSSProtection 2 | from securityheaders.checkers import FindingSeverity, FindingType, Finding 3 | from securityheaders.checkers.xxssprotection import XXSSProtectionChecker 4 | 5 | 6 | class XXSSProtectionHTTPSReportChecker(XXSSProtectionChecker): 7 | def check(self, headers, opt_options=dict()): 8 | xxss = self.getxxss(headers) 9 | 10 | if xxss and xxss.report() and xxss.report().startswith('http:'): 11 | return [Finding(XXSSProtection.headerkey, FindingType.HTTP_REPORT,'This header sets the configuration for the cross-site scripting filter built into most browsers. Violations should be reported to an HTTPS endpoint".',FindingSeverity.LOW, XXSSProtectionDirective.REPORT, xxss.report()) ] 12 | return [] 13 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpcdp/notnone.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.xpcdp import XPermittedCrossDomainPolicies 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from securityheaders.checkers.xpcdp import XPermittedCrossDomainPolicyChecker 4 | 5 | class XPCDPNotNoneChecker(XPermittedCrossDomainPolicyChecker): 6 | def check(self, headers, opt_options=dict()): 7 | xpcdp = self.getxpcdp(headers) 8 | 9 | if not xpcdp: 10 | return [] 11 | 12 | if not xpcdp.is_none(): 13 | directives = [str(x) for x in xpcdp.keys()] 14 | return [Finding(XPermittedCrossDomainPolicies.headerkey, FindingType.INSECURE_HEADER, 'The policy is not set to none. Make sure that you do not use flash.', FindingSeverity.MEDIUM_MAYBE, XPermittedCrossDomainPolicies.directive.NONE, ",".join(directives))] 15 | return [] 16 | -------------------------------------------------------------------------------- /securityheaders/models/xdownloadoptions/xdodirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import requireddirectives 3 | 4 | @requireddirectives('noopen') 5 | class XDownloadOptionsDirective(Directive): 6 | NOOPEN = 'noopen' 7 | 8 | @classmethod 9 | def directiveseperator(cls): 10 | return ',' 11 | 12 | @classmethod 13 | def directivevalueseperator(cls): 14 | return ',' 15 | 16 | @classmethod 17 | def isDirective(cls, directive): 18 | """ Checks whether a given string is a directive 19 | 20 | Args: 21 | directive (str): the string to validate 22 | """ 23 | if isinstance(directive, XDownloadOptionsDirective): 24 | return True 25 | return any(directive.lower() == item.value.lower() for item in cls) 26 | 27 | -------------------------------------------------------------------------------- /securityheaders/checkers/xcontenttypeoptions/nosniff.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingSeverity, FindingType 2 | from securityheaders.models import XContentTypeOptions 3 | from .checker import XContentTypeOptionsChecker 4 | 5 | class XContentTypeOptionsNoSniffChecker(XContentTypeOptionsChecker): 6 | def check(self, headers, opt_options=dict()): 7 | opts = self.getxcontenttypeoptions(headers) 8 | if not opts: 9 | return [] 10 | if not opts.nosniff(): 11 | directive = opts.directives()[0] if opts.directives() else None 12 | return [Finding(XFrameOptions.headerkey, FindingType.NOSNIFF, 'This header stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type. The only valid value for this header is "X-Content-Type-Options: nosniff"' ,FindingSeverity.MEDIUM, directive, None)] 13 | return [] 14 | -------------------------------------------------------------------------------- /securityheaders/models/expectct/expectctdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import requireddirectives, requireddirectivevalues 3 | 4 | @requireddirectives('max-age') 5 | @requireddirectivevalues('max-age','report-uri') 6 | class ExpectCTDirective(Directive): 7 | REPORT_URI = 'report-uri' 8 | ENFORCE = 'enforce' 9 | MAX_AGE = 'max-age' 10 | 11 | 12 | @classmethod 13 | def isDirective(cls, directive): 14 | if isinstance(directive, ExpectCTDirective): 15 | return True 16 | return any(directive.lower() == item.value.lower() for item in list(cls)) 17 | 18 | 19 | @classmethod 20 | def directiveseperator(cls): 21 | return ',' 22 | 23 | @classmethod 24 | def valueseperator(cls): 25 | return '=' 26 | 27 | @classmethod 28 | def directivevalueseperator(cls): 29 | return '=' 30 | -------------------------------------------------------------------------------- /securityheaders/checkers/xframeoptions/good.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import FindingSeverity, Finding, FindingType 2 | from securityheaders.models import XFrameOptions 3 | 4 | from .checker import XFrameOptionsChecker 5 | 6 | class XFrameOptionsNotAllowFromChecker(XFrameOptionsChecker): 7 | def check(self, headers, opt_options=dict()): 8 | opts = self.getxframeoptions(headers) 9 | if not opts: 10 | return [] 11 | if opts.allowfrom(): 12 | directive = opts.directives()[0] if opts.directives() else None 13 | return [Finding(XFrameOptions.headerkey, FindingType.ALLOW_FROM, 'This header tells the browser whether you want to allow your site to be framed or not. By preventing a browser from framing your site you can defend against attacks like clickjacking. The recommended value is "x-frame-options: SAMEORIGIN."' ,FindingSeverity.MEDIUM_MAYBE, directive, None)] 14 | return [] 15 | 16 | -------------------------------------------------------------------------------- /securityheaders/models/cors/alloworigin/accesscontrolalloworigin.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolalloworigindirective import AccessControlAllowOriginDirective 4 | 5 | @description('TODO') 6 | @headername('access-control-allow-origin') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlAllowOrigin(SecurityHeader): 9 | directive = AccessControlAllowOriginDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlAllowOriginDirective) 13 | 14 | def origins(self): 15 | return self.keys() 16 | 17 | def isstar(self): 18 | return self.origins() and self.origins()[0] is AccessControlAllowOriginDirective.STAR 19 | 20 | def isnull(self): 21 | return self.origins() and self.origins()[0] is AccessControlAllowOriginDirective.NULL 22 | 23 | -------------------------------------------------------------------------------- /securityheaders/models/xpcdp/xpcdpdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | class XPermittedCrossDomainPoliciesDirective(Directive): 4 | NONE = 'none' 5 | MASTER_ONLY = 'master-only' 6 | BY_CONTENT_TYPE = 'by-content-type' 7 | BY_FTP_FILENAME = 'by-ftp-filename' 8 | ALL = 'all' 9 | 10 | @classmethod 11 | def directiveseperator(cls): 12 | return ',' 13 | 14 | @classmethod 15 | def directivevalueseperator(cls): 16 | return ',' 17 | 18 | @classmethod 19 | def isDirective(cls, directive): 20 | """ Checks whether a given string is a directive 21 | 22 | Args: 23 | directive (str): the string to validate 24 | """ 25 | if isinstance(directive, XPermittedCrossDomainPoliciesDirective): 26 | return True 27 | return any(directive.lower() == item.value.lower() for item in cls) 28 | 29 | -------------------------------------------------------------------------------- /securityheaders/models/clearsitedata/csddirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | class ClearSiteDataDirective(Directive): 4 | CACHE = 'cache','"cache"' 5 | COOKIES = 'cookies','"cookies"' 6 | STORAGE = 'storage','"storage"' 7 | EXECUTIONCONTEXTS = 'executioncontexts','"executioncontexts"' 8 | STAR = '*','"*"' 9 | 10 | @classmethod 11 | def directiveseperator(cls): 12 | return ',' 13 | 14 | @classmethod 15 | def directivevalueseperator(cls): 16 | return ',' 17 | 18 | @classmethod 19 | def isDirective(cls, directive): 20 | """ Checks whether a given string is a directive 21 | 22 | Args: 23 | directive (str): the string to validate 24 | """ 25 | if isinstance(directive, ClearSiteDataDirective): 26 | return True 27 | return any(directive.lower() == item.value.lower() for item in cls) 28 | 29 | -------------------------------------------------------------------------------- /securityheaders/models/hsts/hstsdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import requireddirectives, requireddirectivevalues 3 | 4 | @requireddirectives('max-age') 5 | @requireddirectivevalues('max-age') 6 | class HSTSDirective(Directive): 7 | INCLUDESUBDOMAINS = 'includesubdomains' 8 | MAX_AGE = 'max-age' 9 | PRELOAD = 'preload' 10 | 11 | @classmethod 12 | def valueseperator(cls): 13 | return '=' 14 | 15 | @classmethod 16 | def directivevalueseperator(cls): 17 | return '=' 18 | 19 | @classmethod 20 | def isDirective(cls, directive): 21 | """ Checks whether a given string is a directive 22 | 23 | Args: 24 | directive (str): the string to validate 25 | """ 26 | if isinstance(directive, HSTSDirective): 27 | return True 28 | return any(directive.lower() == item.value.lower() for item in cls) 29 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_unsafeeval.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckUnsafeEval(CSPCheck): 6 | 7 | def __init__(self, csp, directive, effectiveDirectiveValues): 8 | self.csp = csp 9 | self.directive = directive 10 | self.values = effectiveDirectiveValues 11 | 12 | def check(self): 13 | csp = self.csp 14 | if not csp or not csp.parsedstring: 15 | return [] 16 | 17 | # Check if unsafe-eval is present. 18 | if csp.keyword.UNSAFE_EVAL in self.values or str(csp.keyword.UNSAFE_EVAL) in self.values: 19 | return [Finding(csp.headerkey, FindingType.SCRIPT_UNSAFE_EVAL, csp.keyword.UNSAFE_EVAL.value + ' allows the execution of code injected into DOM APIs such as eval().',FindingSeverity.MEDIUM_MAYBE, self.directive, csp.keyword.UNSAFE_EVAL.value)] 20 | return [] 21 | -------------------------------------------------------------------------------- /securityheaders/models/cors/exposeheaders/accesscontrolexposeheaders.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.annotations import * 3 | from .accesscontrolexposeheadersdirective import AccessControlExposeHeadersDirective 4 | 5 | @description('An application can expose additional HTTP response headers to JavaScript by setting the Access-Control-Expose-Headers header to header names that need to be exposed.') 6 | @headername('access-control-expose-headers') 7 | @headerref('https://fetch.spec.whatwg.org/') 8 | class AccessControlExposeHeaders(SecurityHeader): 9 | directive = AccessControlExposeHeadersDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, AccessControlExposeHeadersDirective) 13 | 14 | def headers(self): 15 | if self.parsedstring: 16 | return self.keys() 17 | return [] 18 | 19 | def __repr__(self): 20 | return ", ".join(self.headers()) 21 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/maxage/toolong.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlMaxAgeDirective, AccessControlMaxAge 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from .checker import AccessControlMaxAgeChecker 4 | 5 | class AccessControlMaxAgeTooLongChecker(AccessControlMaxAgeChecker): 6 | def check(self, headers, opt_options=dict()): 7 | maxage = self.getmaxage(headers) 8 | if maxage and maxage.maxage() and maxage.maxage() > 1800: 9 | return [Finding(AccessControlMaxAge.headerkey, FindingType.MAX_AGE_TOO_LONG, str(AccessControlMaxAge.headerkey) + " set to a too large value. This header is used by the server to explicitly instruct browsers to cache responses to CORS requests. An excessively long cache timeout increases the risk that changes to a server's CORS policy will not be honored as they still use a cached response.",FindingSeverity.LOW, AccessControlMaxAgeDirective, str(maxage.maxage()))] 10 | return [] 11 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_unsafeinline.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckUnsafeInline(CSPCheck): 6 | 7 | def __init__(self, csp, directive, effectiveDirectiveValues): 8 | self.csp = csp 9 | self.directive = directive 10 | self.values = effectiveDirectiveValues 11 | 12 | def check(self): 13 | csp = self.csp 14 | if not csp or not csp.parsedstring: 15 | return [] 16 | 17 | 18 | # Check if unsafe-inline is present. 19 | if csp.keyword.UNSAFE_INLINE in self.values or str(csp.keyword.UNSAFE_INLINE) in self.values: 20 | return [Finding(csp.headerkey, FindingType.SCRIPT_UNSAFE_INLINE, '\'' + csp.keyword.UNSAFE_INLINE.value + '\' allows the execution of unsafe in-page scripts and event handlers.',FindingSeverity.HIGH, self.directive, csp.keyword.UNSAFE_INLINE.value)] 21 | return [] 22 | -------------------------------------------------------------------------------- /securityheaders/checkers/headeremptychecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import SyntaxChecker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory, SecurityHeader 3 | 4 | class HeaderEmptyChecker(SyntaxChecker): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | headernames = ModelFactory().getheadernames() 8 | 9 | findings = [] 10 | for header in headernames: 11 | hdr = ModelFactory().getheader(header) 12 | 13 | try: 14 | obj = self.extractheader(headers, hdr) 15 | findings.extend(self.mycheck(obj)) 16 | except: 17 | pass 18 | return findings 19 | 20 | def mycheck(self, data): 21 | if not data: 22 | return [] 23 | if not data.hasdirectives() and isinstance(data, SecurityHeader): 24 | return [Finding(data.headerkey,FindingType.MISSING_HEADER,str(data.headerkey) + ' header is empty.', FindingSeverity.INFO, data.headerkey)] 25 | return [] 26 | -------------------------------------------------------------------------------- /securityheaders/models/xpcdp/xpcdp.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.xpcdp import XPermittedCrossDomainPoliciesDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @description('This header defines a cross-domain policy for clients such as Adobe Flash Player or Adobe Acrobat.') 6 | @headername('x-permitted-cross-domain-policies') 7 | @headerref('https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html') 8 | class XPermittedCrossDomainPolicies(SecurityHeader): 9 | directive = XPermittedCrossDomainPoliciesDirective 10 | 11 | def __init__(self, unparsedstring): 12 | SecurityHeader.__init__(self, unparsedstring, XPermittedCrossDomainPolicies.directive) 13 | 14 | def is_none(self): 15 | try: 16 | if self.parsedstring: 17 | return XPermittedCrossDomainPoliciesDirective.NONE in self.keys() 18 | return False 19 | except: 20 | pass 21 | return False 22 | 23 | -------------------------------------------------------------------------------- /securityheaders/models/xcontenttypeoptions/xcontenttypeoptions.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.xcontenttypeoptions import XContentTypeOptionsDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('This header stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type. The only valid value for this header is "X-Content-Type-Options: nosniff".') 7 | @headername('x-content-type-options') 8 | @headerref('http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx') 9 | class XContentTypeOptions(SecurityHeader): 10 | directive = XContentTypeOptionsDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, XContentTypeOptionsDirective) 14 | 15 | def nosniff(self): 16 | try: 17 | return XContentTypeOptionsDirective.NOSNIFF in self.parsedstring 18 | except error: 19 | return False 20 | -------------------------------------------------------------------------------- /securityheaders/checkers/xpoweredby/test_present.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from securityheaders.checkers import XPoweredByPresentChecker 3 | 4 | class XPoweredByPresentCheckerTest(unittest.TestCase): 5 | def setUp(self): 6 | self.x = XPoweredByPresentChecker() 7 | 8 | def test_checkNoCSP(self): 9 | nox = dict() 10 | nox['test'] = 'value' 11 | self.assertEqual(self.x.check(nox), []) 12 | 13 | def test_checkNone(self): 14 | nonex = None 15 | self.assertEqual(self.x.check(nonex), []) 16 | 17 | def test_NoneValue(self): 18 | hasx = dict() 19 | hasx['x-powered-by'] = None 20 | result = self.x.check(hasx) 21 | self.assertIsNotNone(result) 22 | self.assertEqual(len(result), 1) 23 | 24 | def test_ValidValue(self): 25 | hasx2 = dict() 26 | hasx2['x-powered-by'] = "Apache" 27 | result = self.x.check(hasx2) 28 | self.assertIsNotNone(result) 29 | self.assertEqual(len(result), 1) 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/test_httpreporturi.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.expectct import ExpectCTHTTPReportURIChecker 4 | 5 | class ExpectCTHTTPReportURICheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = ExpectCTHTTPReportURIChecker() 8 | 9 | def test_check(self): 10 | xempty = dict() 11 | self.assertEqual(self.x.check(xempty), []) 12 | 13 | def test_checkNone(self): 14 | xnone = None 15 | self.assertEqual(self.x.check(xnone), []) 16 | 17 | def test_Good(self): 18 | xhas = dict() 19 | xhas['expect-ct'] = 'max-age=10, enforce, report-uri=https://google.com/report' 20 | self.assertEqual(self.x.check(xhas), []) 21 | 22 | def test_Bad(self): 23 | xhasbad = dict() 24 | xhasbad['expect-ct'] = 'max-age=10, enforce, report-uri=http://google.com/report' 25 | result = self.x.check(xhasbad) 26 | self.assertIsNotNone(result) 27 | self.assertEqual(len(result), 1) 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /securityheaders/checkers/headermissingchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class HeaderMissingChecker(Checker): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | headernames = ModelFactory().getheadernames() 9 | for header in headernames: 10 | hdr = ModelFactory().getheader(header) 11 | try: 12 | obj = self.extractheader(headers, hdr) 13 | if not obj: 14 | obj = hdr("") 15 | if hasattr(obj, 'required') and obj.required: 16 | description = obj.description if hasattr(obj,'description') else '' 17 | result = Finding(header, FindingType.MISSING_HEADER, str(obj.headerkey) + ' header not present. ' + str(description),FindingSeverity.INFO, None) 18 | findings.append(result) 19 | except: 20 | pass 21 | return findings 22 | -------------------------------------------------------------------------------- /securityheaders/checkers/featurepolicy/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | from securityheaders.models.featurepolicy import FeaturePolicy, FeaturePolicyDirective 3 | 4 | class FeaturePolicyChecker(Checker): 5 | 6 | def getfeaturepolicy(self, headers): 7 | return self.extractheader(headers, FeaturePolicy) 8 | 9 | def effectiveDirectiveValues(self, headers, actualdirective): 10 | policy = self.geatfeaturepolicy(headers) 11 | if not policy: 12 | return [] 13 | return policy.getEffectiveValues(actualdirective) 14 | 15 | def applyCheckFunktionToDirectives(self, parsedPolicy, check, findings, opt_directives=[]): 16 | directiveNames = [] 17 | if parsedPolicy: 18 | directiveNames =parsedPolicy.keys() 19 | if opt_directives: 20 | directiveNames = opt_directives 21 | for directive in directiveNames: 22 | directiveValues = parsedPolicy[directive] 23 | if directiveValues: 24 | check(directive, directiveValues, findings) 25 | -------------------------------------------------------------------------------- /securityheaders/models/referrerpolicy/referrerpolicydirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | 3 | class ReferrerPolicyDirective(Directive): 4 | NO_REFERRER = 'no-referrer' 5 | NO_REFERRER_WHEN_DOWNGRADE = 'no-referrer-when-downgrade' 6 | ORIGIN = 'origin' 7 | ORIGIN_WHEN_CROSS_ORIGIN = 'origin-when-cross-origin' 8 | SAME_ORIGIN = 'same-origin' 9 | STRICT_ORIGIN = 'strict-origin' 10 | STRICT_ORIGIN_WHEN_CROSS_ORIGIN = 'strict-origin-when-cross-origin' 11 | UNSAFE_URL = 'unsafe-url' 12 | 13 | @classmethod 14 | def isDirective(cls, directive): 15 | """ Checks whether a given string is a directive 16 | 17 | Args: 18 | directive (str): the string to validate 19 | """ 20 | if isinstance(directive, ReferrerPolicyDirective): 21 | return True 22 | return any(directive.lower() == item for item in cls) 23 | 24 | @classmethod 25 | def directiveseperator(cls): 26 | return ',' 27 | 28 | @classmethod 29 | def directivevalueseperator(cls): 30 | return None 31 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_plainurlschemes.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckPlainUrlSchemes(CSPCheck): 6 | 7 | def __init__(self, csp, effectiveDirectiveValues): 8 | self.csp = csp 9 | self.values = effectiveDirectiveValues 10 | 11 | def check(self): 12 | csp = self.csp 13 | if not csp or not csp.parsedstring: 14 | return [] 15 | findings = [] 16 | directivesToCheck = self.values 17 | for directive in directivesToCheck: 18 | values = [] 19 | if directive in csp.parsedstring: 20 | values = csp[directive] 21 | for value in values: 22 | if value in csp.URL_SCHEMES_CAUSING_XSS: 23 | findings.append(Finding(csp.headerkey, FindingType.PLAIN_URL_SCHEMES, value + ' URI in ' + directive.value + ' allows the execution of unsafe scripts.',FindingSeverity.HIGH, directive, value)) 24 | 25 | return findings 26 | -------------------------------------------------------------------------------- /securityheaders/models/csp/cspversion.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class CSPVersion(Enum): 4 | CSP1 = 1 5 | CSP2 = 2 6 | CSP3 = 3 7 | 8 | def __lt__(self, other): 9 | """ Checks whether another CSPVersion is lower than this version 10 | 11 | Args: 12 | other (CSPVersion): the CSPVersion to compare with 13 | """ 14 | return self.value < other.value 15 | 16 | def __eq__(self, other): 17 | """ Checks whether another CSPVersion is equal to this version 18 | 19 | Args: 20 | other (CSPVersion): the CSPVersion to compare with 21 | """ 22 | return self.value == other.value 23 | 24 | def __ge__(self, other): 25 | """ Checks whether another CSPVersion is greater than this version 26 | 27 | Args: 28 | other (CSPVersion): the CSPVersion to compare with 29 | """ 30 | return self.value > other.value 31 | 32 | def __str__(self): 33 | """ Returns a string representaiton of this CSP Version 34 | """ 35 | return str("CSP Version " + str(self.value)) 36 | -------------------------------------------------------------------------------- /securityheaders/checkers/headerevaluator.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import CheckerFactory 2 | from securityheaders import Util 3 | 4 | class HeaderEvaluator(object): 5 | 6 | def __init__(self): 7 | self.factory = CheckerFactory() 8 | 9 | def evaluate(self,in_headers, opt_options=dict()): 10 | findings = [] 11 | headers = dict() 12 | for header in in_headers: 13 | if len(header) > 1: 14 | headers[header[0].lower()] = header[1] 15 | else: 16 | headers[header[0].lower()] = '' 17 | if 'checks' in opt_options: 18 | checks = opt_options['checks'] 19 | else: 20 | checks = [] 21 | raise Exception("No checks defined") 22 | for check in checks: 23 | checker = self.factory.getchecker(check) 24 | try: 25 | result = checker.check(headers, opt_options) 26 | except: 27 | result = [] 28 | if not result: 29 | result = [] 30 | findings = findings + result 31 | return findings 32 | -------------------------------------------------------------------------------- /securityheaders/checkers/featurepolicy/test_wildcard.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | from securityheaders.checkers.featurepolicy import FeaturePolicyWildCardChecker 5 | 6 | class WildCardTest(unittest.TestCase): 7 | def setUp(self): 8 | self.x = FeaturePolicyWildCardChecker() 9 | 10 | def test_checkNo(self): 11 | nox = dict() 12 | nox['test'] = 'value' 13 | self.assertEqual(self.x.check(nox), []) 14 | 15 | def test_checkNone(self): 16 | nonex = None 17 | self.assertEqual(self.x.check(nonex), []) 18 | 19 | def test_checkNone(self): 20 | hasx = dict() 21 | hasx['feature-policy'] = None 22 | self.assertEqual(self.x.check(hasx), []) 23 | 24 | def test_wildCard(self): 25 | hasx5 = dict() 26 | hasx5['feature-policy'] = 'camera *' 27 | result = self.x.check(hasx5) 28 | self.assertIsNotNone(result) 29 | self.assertEqual(len(result), 1) 30 | 31 | def test_NoWildCard(self): 32 | hasx4 = dict() 33 | hasx4['feature-policy'] = "camera 'self'" 34 | self.assertEqual(self.x.check(hasx4), []) 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/httpcreds.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlAllowOriginDirective, AccessControlAllowOrigin, AccessControlAllowCredentials 2 | from securityheaders.checkers import FindingSeverity, Finding, FindingType 3 | from securityheaders.checkers.cors import AccessControlAllowCredentialsChecker 4 | from .checker import AccessControlAllowOriginChecker 5 | 6 | class AccessControlAllowOriginHTTPCredsChecker(AccessControlAllowOriginChecker, AccessControlAllowCredentialsChecker): 7 | def check(self, headers, opt_options=dict()): 8 | origins = self.getorigins(headers) 9 | hascreds = self.getallowcreds(headers) 10 | findings = [] 11 | 12 | if hascreds and hascreds.value() and origins: 13 | for origin in origins.origins(): 14 | if origin.startswith('http:'): 15 | findings.append(Finding(AccessControlAllowOrigin.headerkey, FindingType.HTTP_ORIGIN, str(AccessControlAllowOrigin.headerkey) + " should be HTTPS rather than HTTP when " + str(AccessControlAllowCredentials.headerkey) + " is true",FindingSeverity.LOW, origin, None)) 16 | return findings 17 | 18 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_wildcard.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckWildCard(CSPCheck): 6 | 7 | def __init__(self, csp): 8 | self.csp = csp 9 | 10 | def check(self): 11 | csp = self.csp 12 | if not csp or not csp.parsedstring: 13 | return [] 14 | 15 | findings = [] 16 | 17 | directivesToCheck = csp.getEffectiveDirectives(csp.DIRECTIVES_CAUSING_XSS) 18 | 19 | for directive in directivesToCheck: 20 | values = [] 21 | if directive in csp.parsedstring: 22 | values = csp[directive] 23 | 24 | for value in values: 25 | url = Util.getSchemeFreeUrl(value) 26 | if '*' in url and len(url) == 1: 27 | findings.append(Finding(csp.headerkey, FindingType.PLAIN_WILDCARD, directive.value + ' should not allow \'*\' as source. This may enable execution of malicious JavaScript.',FindingSeverity.HIGH, directive, value)) 28 | 29 | return findings 30 | -------------------------------------------------------------------------------- /securityheaders/checkers/xframeoptions/test_good.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from securityheaders.checkers.xframeoptions import XFrameOptionsNotAllowFromChecker 3 | 4 | class XFrameOptionsCheckerTest(unittest.TestCase): 5 | def setUp(self): 6 | self.x = XFrameOptionsNotAllowFromChecker() 7 | 8 | def test_checkNoCSP(self): 9 | xempty = dict() 10 | self.assertEqual(self.x.check(xempty), []) 11 | 12 | def test_checkNone(self): 13 | xnone = None 14 | self.assertEqual(self.x.check(xnone), []) 15 | 16 | def test_Good(self): 17 | xhas = dict() 18 | xhas['x-frame-options'] = 'deny' 19 | self.assertEqual(self.x.check(xhas), []) 20 | 21 | def test_Bad(self): 22 | xhasbad = dict() 23 | xhasbad['x-frame-options'] = 'allow-from google.com' 24 | result = self.x.check(xhasbad) 25 | self.assertIsNotNone(result) 26 | self.assertEqual(len(result), 1) 27 | 28 | def test_None(self): 29 | xhasNone = dict() 30 | xhasNone['x-frame-options'] = None 31 | self.assertEqual(self.x.check(xhasNone), []) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /securityheaders/models/setcookie/setcookiedirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import * 3 | 4 | @requireddirectivevalues('expires','max-age','domain','path','samesite') 5 | @anydirective #anything can be a directive (i.e. the cookie name) 6 | class SetCookieDirective(Directive): 7 | EXPIRES='expires' 8 | MAX_AGE = 'max-age' 9 | DOMAIN='domain' 10 | PATH='path' 11 | SECURE='secure' 12 | HTTPONLY='httponly' 13 | SAMESITE ='samesite' 14 | 15 | @classmethod 16 | def directiveseperator(cls): 17 | return ';' 18 | 19 | @classmethod 20 | def directivevalueseperator(cls): 21 | return '=' 22 | 23 | @classmethod 24 | def valueseperator(cls): 25 | return '=' 26 | 27 | @classmethod 28 | def isDirective(cls, directive): 29 | """ Checks whether a given string is a directive 30 | 31 | Args: 32 | directive (str): the string to validate 33 | """ 34 | if isinstance(directive, SetCookieDirective): 35 | return True 36 | return any(directive.lower() == item.value.lower() for item in cls) 37 | 38 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_srchttp.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckSrcHttp(CSPCheck): 6 | 7 | def __init__(self, csp, function): 8 | self.csp = csp 9 | self.function = function 10 | 11 | def check(self): 12 | csp = self.csp 13 | if not csp: 14 | return [] 15 | 16 | findings = [] 17 | 18 | self.function(csp.parsedstring, self.checksrchttp, findings) 19 | return findings 20 | 21 | def checksrchttp(self, directive, directiveValues, findings): 22 | csp = self.csp 23 | for value in directiveValues: 24 | description = None 25 | if directive == csp.directive.REPORT_URI: 26 | description = 'Use HTTPS to send violation reports securely.' 27 | else: 28 | description = 'Allow only resources downloaded over HTTPS.' 29 | if value.startswith('http://'): 30 | findings.append(Finding(csp.headerkey, FindingType.SRC_HTTP,description,FindingSeverity.MEDIUM, directive, value)) 31 | -------------------------------------------------------------------------------- /securityheaders/checkers/infourlcollector.py: -------------------------------------------------------------------------------- 1 | #Collects all used URLs in a policy 2 | from securityheaders.checkers import InfoCollector, Finding, FindingType, FindingSeverity 3 | from securityheaders.models import ModelFactory 4 | 5 | class InfoURLCollector(InfoCollector): 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | 9 | headernames = ModelFactory().getheadernames() 10 | for header in headernames: 11 | hdr = ModelFactory().getheader(header) 12 | try: 13 | obj = self.extractheader(headers, hdr) 14 | 15 | if obj and obj.parsedstring: 16 | if hasattr(obj, 'getdirectives') and hasattr(obj,'geturls'): 17 | for directive in obj.getdirectives(): 18 | urls = obj.geturls([directive]) 19 | if not urls: 20 | urls = [] 21 | for url in urls: 22 | findings.append(Finding(obj.headerkey, FindingType.INFO_URL, str(url), FindingSeverity.NONE, directive, str(url) )) 23 | except: 24 | pass 25 | 26 | return findings 27 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/test_null.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.cors import AccessControlAllowOriginNullChecker 4 | 5 | class AccessControlAllowOriginNullCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = AccessControlAllowOriginNullChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['access-control-allow-origin'] = None 21 | self.assertEqual(self.x.check(hasx), []) 22 | 23 | def test_checkInvalid(self): 24 | hasx2 = dict() 25 | hasx2['access-control-allow-origin'] = "null" 26 | result = self.x.check(hasx2) 27 | self.assertIsNotNone(result) 28 | self.assertEqual(len(result), 1) 29 | 30 | def test_checkValid(self): 31 | hasx5 = dict() 32 | hasx5['access-control-allow-origin'] = "https://www.google.com, https://www.facebook.com" 33 | self.assertEqual(self.x.check(hasx5), []) 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/test_star.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.cors import AccessControlAllowOriginStarChecker 4 | 5 | class AccessControlAllowOriginStarCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = AccessControlAllowOriginStarChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['access-control-allow-origin'] = None 21 | self.assertEqual(self.x.check(hasx), []) 22 | 23 | def test_checkInvalid(self): 24 | hasx2 = dict() 25 | hasx2['access-control-allow-origin'] = "*" 26 | result = self.x.check(hasx2) 27 | self.assertIsNotNone(result) 28 | self.assertEqual(len(result), 1) 29 | 30 | def test_checkValid(self): 31 | hasx5 = dict() 32 | hasx5['access-control-allow-origin'] = "https://www.google.com, https://www.facebook.com" 33 | self.assertEqual(self.x.check(hasx5), []) 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/test_maxagezero.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.hsts import HSTSMaxAgeZeroChecker 4 | 5 | class HSTSMaxAgeZeroCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = HSTSMaxAgeZeroChecker() 8 | 9 | def test_checkNoHSTS(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNoneHSTS(self): 19 | hasx = dict() 20 | hasx['strict-transport-security'] = None 21 | self.assertEqual(self.x.check(hasx), []) 22 | 23 | def test_ValidHSTS(self): 24 | hasx4 = dict() 25 | hasx4['strict-transport-security'] = "max-age=31536000; includeSubDomains" 26 | result = self.x.check(hasx4) 27 | self.assertEqual(self.x.check(hasx4), []) 28 | 29 | def test_ZeroMaxAge(self): 30 | hasx5 = dict() 31 | hasx5['strict-transport-security'] = "max-age=0; includeSubDomains" 32 | result = self.x.check(hasx5) 33 | self.assertIsNotNone(result) 34 | self.assertEqual(len(result), 1) 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/test_xwebkitcspdeprecated.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.other import XWebKitCSPDeprecatedChecker 4 | 5 | class XWebKitCSPDeprecatedCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = XWebKitCSPDeprecatedChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['x-webkit-csp'] = None 21 | result = self.x.check(hasx) 22 | self.assertIsNotNone(result) 23 | self.assertEqual(len(result), 1) 24 | 25 | def test_checkValid(self): 26 | hasx5 = dict() 27 | hasx5['x-webkit-csp'] = "default-src: 'none'" 28 | result = self.x.check(hasx5) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_checkOther(self): 33 | hasx5 = dict() 34 | hasx5['content-security-policy'] = "default-src: 'none'" 35 | self.assertEqual(self.x.check(hasx5), []) 36 | 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/test_xcspdeprecated.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.other import XCSPDeprecatedChecker 4 | 5 | class XCSPDeprecatedCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = XCSPDeprecatedChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['x-content-security-policy'] = None 21 | result = self.x.check(hasx) 22 | self.assertIsNotNone(result) 23 | self.assertEqual(len(result), 1) 24 | 25 | def test_checkValid(self): 26 | hasx5 = dict() 27 | hasx5['x-content-security-policy'] = "default-src: 'none'" 28 | result = self.x.check(hasx5) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_checkOther(self): 33 | hasx5 = dict() 34 | hasx5['content-security-policy'] = "default-src: 'none'" 35 | self.assertEqual(self.x.check(hasx5), []) 36 | 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /securityheaders/models/expectct/expectct.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.expectct import ExpectCTDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('Expect-CT allows a site to determine if they are ready to enforce their CT policy.') 7 | @headername('expect-ct') 8 | @headerref('https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-07') 9 | class ExpectCT(SecurityHeader): 10 | directive = ExpectCTDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, ExpectCTDirective) 14 | 15 | def enforce(self): 16 | try: 17 | result = ExpectCTDirective.ENFORCE in self.parsedstring 18 | return result 19 | except: 20 | return False 21 | 22 | def reporturi(self): 23 | try: 24 | return self.parsedstring[ExpectCTDirective.REPORT_URI][0] 25 | except IndexError: 26 | return "" #there is a key, but it is empty 27 | except KeyError: 28 | return None #there is no key 29 | 30 | def maxage(self): 31 | try: 32 | return int(self.parsedstring[ExpectCTDirective.MAX_AGE][0]) 33 | except Exception: 34 | return None 35 | -------------------------------------------------------------------------------- /securityheaders/checkers/hsts/test_subdomains.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.hsts import HSTSSubdomainsChecker 4 | 5 | class HSTSSubdomainsCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = HSTSSubdomainsChecker() 8 | 9 | def test_checkNoHSTS(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNoneHSTS(self): 19 | hasx = dict() 20 | hasx['strict-transport-security'] = None 21 | result = self.x.check(hasx) 22 | self.assertIsNotNone(result) 23 | self.assertEqual(len(result), 1) 24 | 25 | def test_ValidHSTS(self): 26 | hasx4 = dict() 27 | hasx4['strict-transport-security'] = "max-age=31536000; includeSubDomains" 28 | result = self.x.check(hasx4) 29 | self.assertEqual(self.x.check(hasx4), []) 30 | 31 | def test_NoSubdomains(self): 32 | hasx5 = dict() 33 | hasx5['strict-transport-security'] = "max-age=31536000" 34 | result = self.x.check(hasx5) 35 | self.assertIsNotNone(result) 36 | self.assertEqual(len(result), 1) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /securityheaders/checkers/referrerpolicy/insecure.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders.models import ReferrerPolicy 3 | from securityheaders.checkers.referrerpolicy import ReferrerPolicyChecker 4 | 5 | 6 | class ReferrerPolicyInsecureChecker(ReferrerPolicyChecker): 7 | def check(self, headers, opt_options=dict()): 8 | findings = [] 9 | policy = self.getreferrerpolicy(headers) 10 | 11 | if not policy: 12 | return findings 13 | if policy.unsafe_url(): 14 | findings.append(Finding(ReferrerPolicy.headerkey, FindingType.UNSAFE_URL, 'If this policy is set, it should not use unsafe-url and origin-when-cross-origin as it can transfer sensitive information (via the Referer header) from HTTPS environments to HTTP environments.', FindingSeverity.LOW, ReferrerPolicy.directive.UNSAFE_URL)) 15 | if policy.origin_when_cross_origin(): 16 | findings.append(Finding(ReferrerPolicy.headerkey, FindingType.ORIGIN_WHEN_CROSS_ORIGIN, 'If this policy is set, it should not use unsafe-url and origin-when-cross-origin as it can transfer sensitive information (via the Referer header) from HTTPS environments to HTTP environments.', FindingSeverity.LOW, ReferrerPolicy.directive.ORIGIN_WHEN_CROSS_ORIGIN)) 17 | return findings 18 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/test_block.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.xxssprotection import XXSSProtectionBlockChecker 4 | 5 | class XXSSProtectionBlockCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = XXSSProtectionBlockChecker() 8 | 9 | def test_checkNoCSP(self): 10 | xempty = dict() 11 | self.assertEqual(self.x.check(xempty), []) 12 | 13 | def test_checkNone(self): 14 | xnone = None 15 | self.assertEqual(self.x.check(xnone), []) 16 | 17 | def test_Good(self): 18 | xhas = dict() 19 | xhas['x-xss-protection'] = '1' 20 | self.assertEqual(self.x.check(xhas), []) 21 | 22 | def test_Good2(self): 23 | xhas = dict() 24 | xhas['x-xss-protection'] = '1; mode=block' 25 | self.assertEqual(self.x.check(xhas), []) 26 | 27 | def test_Good3(self): 28 | xhas = dict() 29 | xhas['x-xss-protection'] = '1; report=https://example.com/rep' 30 | self.assertEqual(self.x.check(xhas), []) 31 | 32 | def test_NotEnabled(self): 33 | xhas = dict() 34 | xhas['x-xss-protection'] = '0' 35 | result = self.x.check(xhas) 36 | self.assertIsNotNone(result) 37 | self.assertEqual(len(result), 1) 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /securityheaders/checkers/xxssprotection/test_httpsreport.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | from securityheaders.checkers.xxssprotection import XXSSProtectionHTTPSReportChecker 5 | 6 | class XXSSProtectionHTTPSReportCheckerTest(unittest.TestCase): 7 | def setUp(self): 8 | self.x = XXSSProtectionHTTPSReportChecker() 9 | 10 | def test_checkNoCSP(self): 11 | xempty = dict() 12 | self.assertEqual(self.x.check(xempty), []) 13 | 14 | def test_checkNone(self): 15 | xnone = None 16 | self.assertEqual(self.x.check(xnone), []) 17 | 18 | def test_Good(self): 19 | xhas = dict() 20 | xhas['x-xss-protection'] = '1' 21 | self.assertEqual(self.x.check(xhas), []) 22 | 23 | def test_Good2(self): 24 | xhas = dict() 25 | xhas['x-xss-protection'] = '1; mode=block' 26 | self.assertEqual(self.x.check(xhas), []) 27 | 28 | def test_Good3(self): 29 | xhas = dict() 30 | xhas['x-xss-protection'] = '1; report=https://example.com/rep' 31 | self.assertEqual(self.x.check(xhas), []) 32 | 33 | def test_HTTP(self): 34 | xhas = dict() 35 | xhas['x-xss-protection'] = '1; report=http://example.com/repo' 36 | result = self.x.check(xhas) 37 | self.assertIsNotNone(result) 38 | self.assertEqual(len(result), 1) 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /securityheaders/checkers/infodirectivecollector.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import InfoCollector, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class InfoDirectiveCollector(InfoCollector): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | headernames = ModelFactory().getheadernames() 8 | findings = [] 9 | for header in headernames: 10 | hdr = ModelFactory().getheader(header) 11 | try: 12 | obj = self.extractheader(headers, hdr) 13 | if obj and obj.parsedstring: 14 | findings.extend(self.mycheck(obj)) 15 | except: 16 | pass 17 | return findings 18 | 19 | def mycheck(self, data): 20 | findings = [] 21 | 22 | if not data: 23 | return findings 24 | 25 | for mydirective in data.keys(): 26 | if data.directive.isDirective(mydirective): 27 | value = data[mydirective] 28 | if value: 29 | valstr = '' 30 | for val in value: 31 | valstr = valstr + ' ' + str(val) 32 | else: 33 | valstr = "" 34 | findings.append(Finding(data.headerkey, FindingType.INFO_DIRECTIVE,valstr,FindingSeverity.NONE,mydirective)) 35 | return findings 36 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/maxage/test_toolong.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.cors import AccessControlMaxAgeTooLongChecker 4 | 5 | class AccessControlMaxAgeTooLongCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = AccessControlMaxAgeTooLongChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['access-control-max-age'] = None 21 | self.assertEqual(self.x.check(hasx), []) 22 | 23 | def test_checkInvalid(self): 24 | hasx2 = dict() 25 | hasx2['access-control-max-age'] = "20000" 26 | result = self.x.check(hasx2) 27 | self.assertIsNotNone(result) 28 | self.assertEqual(len(result), 1) 29 | 30 | def test_checkValid(self): 31 | hasx5 = dict() 32 | hasx5['access-control-max-age'] = "100" 33 | result = self.x.check(hasx5) 34 | self.assertEqual(result, []) 35 | 36 | def test_checkValid2(self): 37 | hasx5 = dict() 38 | hasx5['access-control-max-age'] = "-1" 39 | self.assertEqual(self.x.check(hasx5), []) 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/notenforce.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import ExpectCT 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | 4 | from .checker import ExpectCTChecker 5 | 6 | class ExpectCTNotEnforcedChecker(ExpectCTChecker): 7 | 8 | def check(self, headers, opt_options=dict()): 9 | findings = [] 10 | expectct = self.getexpectct(headers) 11 | 12 | if not expectct: 13 | return findings 14 | 15 | findings = [] 16 | if not expectct.enforce(): 17 | findings.append(Finding(expectct.headerkey,FindingType.NOT_ENFORCED,expectct.headerkey + 'is not enforced as ' + ExpectCT.directive.ENFORCE.value + ' is not set.', FindingSeverity.LOW, ExpectCT.directive.ENFORCE,None)) 18 | if expectct.maxage() == 0: 19 | findings.append(Finding(expectct.headerkey,FindingType.NOT_ENFORCED,expectct.headerkey + 'is not enforced as ' + ExpectCT.directive.MAX_AGE.value + ' is set to 0', FindingSeverity.LOW, ExpectCT.directive.MAX_AGE,'0')) 20 | elif expectct.maxage() and expectct.maxage() < 3000: 21 | findings.append(Finding(expectct.headerkey,FindingType.NOT_ENFORCED,expectct.headerkey + 'is only enforced for a very short amount of time as ' + ExpectCT.directive.MAX_AGE.value + ' is set to ' + str(expectct.maxage()), FindingSeverity.LOW, ExpectCT.directive.MAX_AGE,str(expectct.maxage()))) 22 | return findings 23 | -------------------------------------------------------------------------------- /securityheaders/checkers/featurepolicy/wildcard.py: -------------------------------------------------------------------------------- 1 | #checks whether * has been used 2 | from securityheaders import Util 3 | from securityheaders.models.featurepolicy import FeaturePolicy, FeaturePolicyKeyword, FeaturePolicyDirective 4 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 5 | 6 | from .checker import FeaturePolicyChecker 7 | 8 | class FeaturePolicyWildCardChecker(FeaturePolicyChecker): 9 | def check(self, headers, opt_options=dict()): 10 | policy = self.getfeaturepolicy(headers) 11 | if not policy or not policy.parsedstring: 12 | return [] 13 | 14 | findings = [] 15 | 16 | directivesToCheck = policy.getEffectiveDirectives() 17 | 18 | for directive in directivesToCheck: 19 | values = policy.getEffectiveValues(directive) 20 | 21 | for value in values: 22 | url = Util.getSchemeFreeUrl(value) 23 | if url and str(FeaturePolicyKeyword.STAR) in url and len(url) == 1 and directive != FeaturePolicyDirective.PICTURE_IN_PICTURE: 24 | findings.append(Finding(policy.headerkey, FindingType.PLAIN_WILDCARD, directive.value + ' should not allow \'*\' as source. It enables the current page and nesting contexts, such as iframes, to use the feature. It may be better to disable and explicitly tell the iframe which feature is allowed.',FindingSeverity.LOW, directive, value)) 25 | 26 | return findings 27 | 28 | -------------------------------------------------------------------------------- /securityheaders/models/annotations.py: -------------------------------------------------------------------------------- 1 | def requiredheader(cls): 2 | cls.required = True 3 | return cls 4 | 5 | def requireddirective(cls): 6 | cls.requireddirective = True 7 | return cls 8 | 9 | def anydirective(cls): 10 | cls.anydirective = True 11 | return cls 12 | 13 | class requireddirectives: 14 | def __init__(self, *args): 15 | self.values = list(args) 16 | 17 | def __call__(self, fn, *args, **kwargs): 18 | fn.requireddirectives = self.values 19 | return fn 20 | 21 | class requireddirectivevalues: 22 | def __init__(self, *args): 23 | self.values = list(args) 24 | 25 | def __call__(self, fn, *args, **kwargs): 26 | fn.requireddirectivevalues = self.values 27 | return fn 28 | 29 | class description: 30 | def __init__(self, value): 31 | self.value = value 32 | 33 | def __call__(self, fn, *args, **kwargs): 34 | fn.description = self.value 35 | return fn 36 | 37 | class headername: 38 | def __init__(self, value): 39 | self.value = value 40 | 41 | def __call__(self, fn, *args, **kwargs): 42 | fn.headerkey = self.value 43 | return fn 44 | 45 | 46 | class headerref: 47 | def __init__(self, value): 48 | self.value = value 49 | 50 | def __call__(self, fn, *args, **kwargs): 51 | if not hasattr(fn, 'headerrefs'): 52 | fn.headerrefs = [] 53 | fn.headerrefs.append(self.value) 54 | return fn 55 | -------------------------------------------------------------------------------- /securityheaders/checkers/directivemissingchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import SyntaxChecker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class MissingDirectiveChecker(SyntaxChecker): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | headernames = ModelFactory().getheadernames() 9 | for header in headernames: 10 | hdr = ModelFactory().getheader(header) 11 | try: 12 | obj = self.extractheader(headers, hdr) 13 | if obj and obj.parsedstring: 14 | findings.extend(self.mycheck(obj)) 15 | except: 16 | pass 17 | return findings 18 | 19 | def mycheck(self, data): 20 | if not hasattr(data, 'directive'): 21 | return [] 22 | directive = data.directive 23 | 24 | if not hasattr(directive, 'requireddirectives'): 25 | return [] 26 | if not directive.requireddirectives: 27 | return [] 28 | result = [] 29 | for required in directive.requireddirectives: 30 | required = directive(required) 31 | allkeys = [str(key) for key in data.keys()] 32 | if not required in data.keys(): 33 | result.append(Finding(data.headerkey, FindingType.MISSING_DIRECTIVES,str(required) + ' directive is missing.',FindingSeverity.SYNTAX,required,",".join(allkeys))) 34 | return result 35 | -------------------------------------------------------------------------------- /securityheaders/models/directive.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class Directive(Enum): 4 | 5 | @classmethod 6 | def __new__(cls, *values): 7 | obj = object.__new__(cls) 8 | # first value is canonical value 9 | obj._value_ = values[1].lower() 10 | for other_value in values[1:]: 11 | cls._value2member_map_[other_value.lower()] = obj 12 | obj._all_values = values 13 | return obj 14 | 15 | def __repr__(self): 16 | return self.__str__() 17 | 18 | def endswith(self, value): 19 | return str(self).endswith(value) 20 | 21 | def startswith(self, value): 22 | return str(self).startswith(value) 23 | 24 | def __str__(self): 25 | """ Returns a string representaiton of this CSP Directive 26 | """ 27 | return str(self._value_).lower() 28 | 29 | def lower(self): 30 | return str(self).lower() 31 | 32 | def find(self, value): 33 | return str(self).find(value) 34 | 35 | @classmethod 36 | def keys(cls): 37 | return cls._value2member_map_.keys() 38 | 39 | @classmethod 40 | def directiveseperator(cls): 41 | return ';' 42 | 43 | 44 | @classmethod 45 | def directivevalueseperator(cls): 46 | return ':' 47 | 48 | @classmethod 49 | def valueseperator(cls): 50 | return None 51 | 52 | @classmethod 53 | def isDirective(cls): 54 | pass 55 | 56 | def getDefaultValue(self): 57 | pass 58 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_frameancestors.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckFrameAncestors(CSPCheck): 6 | 7 | def __init__(self, csp, function): 8 | self.csp = csp 9 | self.function = function 10 | 11 | def check(self): 12 | csp = self.csp 13 | if not csp: 14 | return [] 15 | 16 | findings = [] 17 | 18 | self.function(csp.parsedstring, self.checkframing, findings) 19 | return findings 20 | 21 | def checkframing(self, directive, directiveValues, findings): 22 | csp = self.csp 23 | description = "This directive tells the browser whether you want to allow your site to be framed or not. By preventing a browser from framing your site you can defend against attacks like clickjacking. The recommended value is 'none' or 'self'." 24 | for value in directiveValues: 25 | if directive == csp.directive.FRAME_ANCESTORS: 26 | if self.__notcontains_keyword__(value, csp.keyword.NONE) and self.__notcontains_keyword__(value, csp.keyword.SELF): 27 | findings.append(Finding(csp.headerkey, FindingType.ALLOW_FROM,description,FindingSeverity.MEDIUM_MAYBE, directive, value)) 28 | 29 | 30 | def __notcontains_keyword__(self, value, keyword): 31 | return keyword != value and keyword.value not in str(value) 32 | -------------------------------------------------------------------------------- /securityheaders/checkers/other/test_xasp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.other import XASPNetPresentChecker 4 | 5 | class XASPNetPresentCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = XASPNetPresentChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['x-aspnet-version'] = None 21 | result = self.x.check(hasx) 22 | self.assertIsNotNone(result) 23 | self.assertEqual(len(result), 1) 24 | 25 | def test_checkValid(self): 26 | hasx5 = dict() 27 | hasx5['x-aspnet-version'] = "2.0.50727" 28 | result = self.x.check(hasx5) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_checkNone3(self): 33 | hasx = dict() 34 | hasx['x-aspnetmvc-version'] = None 35 | result = self.x.check(hasx) 36 | self.assertIsNotNone(result) 37 | self.assertEqual(len(result), 1) 38 | 39 | def test_checkValid2(self): 40 | hasx5 = dict() 41 | hasx5['X-AspNetMvc-Version'] = "3.0" 42 | result = self.x.check(hasx5) 43 | self.assertIsNotNone(result) 44 | self.assertEqual(len(result), 1) 45 | 46 | 47 | if __name__ == '__main__': 48 | unittest.main() 49 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/alloworigin/test_httpcreds.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.cors import AccessControlAllowOriginHTTPCredsChecker 4 | 5 | class AccessControlAllowOriginHTTPCREDSCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = AccessControlAllowOriginHTTPCredsChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['access-control-allow-origin'] = None 21 | hasx['access-control-allow-credentials'] = "true" 22 | self.assertEqual(self.x.check(hasx), []) 23 | 24 | def test_checkInvalid(self): 25 | hasx2 = dict() 26 | hasx2['access-control-allow-origin'] = "http://buyens.org, http://tweakers.net, https://synopsys.com" 27 | hasx2['access-control-allow-credentials'] = "true" 28 | result = self.x.check(hasx2) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 2) 31 | 32 | def test_checkValid(self): 33 | hasx5 = dict() 34 | hasx5['access-control-allow-origin'] = "https://www.google.com, https://www.facebook.com" 35 | hasx5['access-control-allow-credentials'] = "true" 36 | self.assertEqual(self.x.check(hasx5), []) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /securityheaders/checkers/directiveemptychecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import SyntaxChecker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class EmptyDirectiveChecker(SyntaxChecker): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | findings = [] 8 | headernames = ModelFactory().getheadernames() 9 | for header in headernames: 10 | hdr = ModelFactory().getheader(header) 11 | try: 12 | obj = self.extractheader(headers, hdr) 13 | if obj and obj.parsedstring: 14 | findings.extend(self.mycheck(obj)) 15 | except: 16 | pass 17 | return findings 18 | 19 | def mycheck(self, data): 20 | if not hasattr(data, 'directive'): 21 | return [] 22 | directive = data.directive 23 | 24 | if not hasattr(directive, 'requireddirectivevalues'): 25 | return [] 26 | if not directive.requireddirectivevalues: 27 | return [] 28 | result = [] 29 | for required in directive.requireddirectivevalues: 30 | required = directive(required) 31 | if required in data.keys(): 32 | value = data[required] 33 | if not value: 34 | result.append(Finding(data.headerkey, FindingType.MISSING_VALUES,str(required) + ' is defined, but does not have a value.',FindingSeverity.SYNTAX,required, None)) 35 | return result 36 | -------------------------------------------------------------------------------- /conf/cdn.txt: -------------------------------------------------------------------------------- 1 | //*.clients.turbobytes.net 2 | //*.turbobytes-cdn.com 3 | //*.afxcdn.net 4 | //*.akamai.net 5 | //*.akamaiedge.net 6 | //*.akadns.net 7 | //*.akamaitechnologies.com 8 | //*.gslb.tbcache.com 9 | //*.cloudfront.net 10 | //*.anankecdn.com.br 11 | //*.att-dsa.net 12 | //*.azioncdn.net 13 | //*.belugacdn.com 14 | //*.bluehatnetwork.com 15 | //*.systemcdn.net 16 | //*.cachefly.net 17 | //*.cdn77.net 18 | //*.cdn77.org 19 | //*.panthercdn.com 20 | //*.cdngc.net 21 | //*.gccdn.net 22 | //*.gccdn.cn 23 | //*.cdnify.io 24 | //*.ccgslb.com 25 | //*.ccgslb.net 26 | //*.c3cache.net 27 | //*.chinacache.net 28 | //*.c3cdn.net 29 | //*.lxdns.com 30 | //*.speedcdns.com 31 | //*.mwcloudcdn.com 32 | //*.cloudflare.com 33 | //*.cloudflare.net 34 | //*.edgecastcdn.net 35 | //*.fastly.net 36 | //*.fastlylb.net 37 | //*.googlesyndication.com 38 | //*.googleusercontent.com 39 | //*.l.doubleclick.net 40 | //*.hiberniacdn.com 41 | //*.hwcdn.net 42 | //*.incapdns.net 43 | //*.inscname.net 44 | //*.insnw.net 45 | //*.internapcdn.net 46 | //*.kxcdn.com 47 | //*.lswcdn.net 48 | //*.footprint.net 49 | //*.llnwd.net 50 | //*.lldns.net 51 | //*.netdna-cdn.com 52 | //*.netdna-ssl.com 53 | //*.netdna.com 54 | //*.stackpathdns.com 55 | //*.mncdn.com 56 | //*.instacontent.net 57 | //*.mirror-image.net 58 | //*.cap-mii.net 59 | //*.rncdn1.com 60 | //*.simplecdn.net 61 | //*.swiftcdn1.com 62 | //*.swiftserve.com 63 | //*.gslb.taobao.com 64 | //*.cdn.bitgravity.com 65 | //*.cdn.telefonica.com 66 | //*.vo.msecnd.net 67 | //*.ay1.b.yahoo.com 68 | //*.yimg.com 69 | //*.zenedge.net -------------------------------------------------------------------------------- /securityheaders/models/hsts/hsts.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.hsts import HSTSDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('This header strengthens your implementation of TLS by getting the User Agent to enforce the use of HTTPS. The recommended value us "strict-transport-security: max-age=31536000; includeSubDomains".') 7 | @headername('strict-transport-security') 8 | @headerref('http://tools.ietf.org/html/rfc6797') 9 | class HSTS(SecurityHeader): 10 | directive = HSTSDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, HSTSDirective) 14 | 15 | 16 | def includesubdomains(self): 17 | try: 18 | return HSTSDirective.INCLUDESUBDOMAINS in self.parsedstring 19 | except: 20 | return False 21 | 22 | 23 | def preload(self): 24 | try: 25 | return HSTSDirective.PRELOAD in self.parsedstring 26 | except error: 27 | return False 28 | 29 | 30 | def maxAge(self): 31 | result = None 32 | if self.parsedstring and HSTSDirective.MAX_AGE in self.parsedstring: 33 | if isinstance(self.parsedstring[HSTSDirective.MAX_AGE], list): 34 | try: 35 | result = int(self.parsedstring[HSTSDirective.MAX_AGE][0]) 36 | except: 37 | result = None 38 | else: 39 | result = int(self.parsedstring[HSTSDirective.MAX_AGE]) 40 | return result 41 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/exposeheaders/exposesensitiveheaders.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models.cors import AccessControlExposeHeadersDirective, AccessControlExposeHeaders 2 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 3 | from .checker import AccessControlExposeHeadersChecker 4 | 5 | class AccessControlExposeHeadersSensitiveChecker(AccessControlExposeHeadersChecker): 6 | def check(self, headers, opt_options=dict()): 7 | headers = self.getexposeheaders(headers) 8 | if not headers: 9 | return [] 10 | 11 | result = [] 12 | for header in headers.headers(): 13 | if self.__issensitive__(header): 14 | result.append(Finding(AccessControlExposeHeaders.headerkey, FindingType.SENSITIVE_HEADER_EXPOSED, str(AccessControlExposeHeaders.headerkey) + " exposes sensitive headers to JavaScript. An attacker can deceive the victim into browsing to an untrusted origin containing JavaScript that makes an HTTP request to the target origin. The malicious JavaScript code reads the value of the sensitive header and shares it with the attacker. If the header contains session information, the attacker can hijack the victim's session.",FindingSeverity.MEDIUM, str(header),None)) 15 | return result 16 | 17 | def __issensitive__(self, header): 18 | if 'session' in header: 19 | return True 20 | if 'authentication' in header: 21 | return True 22 | if 'authorization' in header: 23 | return True 24 | if 'token' in header: 25 | return True 26 | return False 27 | -------------------------------------------------------------------------------- /securityheaders/models/xframeoptions/xframeoptions.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.xframeoptions import XFrameOptionsDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('This header tells the browser whether the site can be framed. Not allowing framing defends against clickjacking attacks.') 7 | @headername('x-frame-options') 8 | @headerref('https://tools.ietf.org/html/rfc7034') 9 | class XFrameOptions(SecurityHeader): 10 | directive = XFrameOptionsDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, XFrameOptionsDirective) 14 | 15 | def deny(self): 16 | try: 17 | return XFrameOptionsDirective.DENY in self.parsedstring 18 | except: 19 | return False 20 | 21 | 22 | def sameorigin(self): 23 | try: 24 | return XFrameOptionsDirective.SAMEORIGIN in self.parsedstring 25 | except: 26 | return False 27 | 28 | def allowfrom(self): 29 | result = None 30 | if self.parsedstring and XFrameOptionsDirective.ALLOW_FROM in self.parsedstring: 31 | if isinstance(self.parsedstring[XFrameOptionsDirective.ALLOW_FROM], list): 32 | if len(self.parsedstring[XFrameOptionsDirective.ALLOW_FROM]) > 0: 33 | result = self.parsedstring[XFrameOptionsDirective.ALLOW_FROM][0] 34 | else: 35 | result = "" 36 | else: 37 | result = self.parsedstring[XFrameOptionsDirective.ALLOW_FROM] 38 | return result 39 | -------------------------------------------------------------------------------- /securityheaders/models/featurepolicy/featurepolicydirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from .featurepolicykeyword import FeaturePolicyKeyword 3 | 4 | class FeaturePolicyDirective(Directive): 5 | 6 | ACCELEROMETER = "accelerometer" 7 | AMBIENT_LIGHT_SENSOR = "ambient-light-sensor", 8 | CAMERA = "camera" 9 | ENCRYPTED_MEDIA = "encrypted-media" 10 | FULLSCREEN = "fullscreen" 11 | GEOLOCATION = "geolocation" 12 | GYROSCOPE = "gyroscope" 13 | MAGNETOMETER = "magnetometer" 14 | MICROPHONE = "microphone" 15 | MIDI = "midi" 16 | PAYMENT = "payment" 17 | SPEAKER = "speaker" 18 | SYNC_XHR = "sync-xhr" 19 | USB = "usb" 20 | VR = "vr" 21 | PICTURE_IN_PICTURE = "picture-in-picture" 22 | DOCUMENT_WRITE = "document-write" 23 | IMAGE_COMPRESSION = "image-compression" 24 | LEGACY_IMAGE_FORMATS = "legacy-image-formats" 25 | MAX_DOWNSCALING_IMAGE = "max-downscaling-image" 26 | UNSIZED_MEDIA = "unsized-media" 27 | VERTICAL_SCROLL = "vertical-scroll" 28 | ANIMATIONS = "animations" 29 | AUTOPLAY = "autoplay" 30 | VIBRATE = 'vibrate' 31 | 32 | @classmethod 33 | def isDirective(cls, directive): 34 | """ Checks whether a given string is a directive 35 | 36 | Args: 37 | directive (str): the string to validate 38 | """ 39 | if isinstance(directive, FeaturePolicyDirective): 40 | return True 41 | return any(directive.lower() == item.value.lower() for item in cls) 42 | 43 | 44 | def getDefaultValue(self): 45 | if self == self.PICTURE_IN_PICTURE: 46 | return FeaturePolicyKeyword.STAR 47 | return FeaturePolicyKeyword.SELF 48 | 49 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_whitelistcdn.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckWhitelistCDN(CSPCheck): 6 | def __init__(self, csp): 7 | self.csp = csp 8 | 9 | def check(self, opt_options=dict()): 10 | csp = self.csp 11 | if not csp: 12 | return [] 13 | 14 | findings = [] 15 | cdn = [] 16 | if 'cdn' not in opt_options.keys(): 17 | cdn = [] 18 | elif 'cdn' in opt_options.keys(): 19 | cdn = opt_options['cdn'] 20 | effectiveScriptSrcDirective = csp.getEffectiveDirective(csp.directive.SCRIPT_SRC) 21 | scriptSrcValues = [] 22 | try: 23 | scriptSrcValues = csp[effectiveScriptSrcDirective] 24 | except KeyError: 25 | scriptSrcValues = [] 26 | for value in scriptSrcValues: 27 | if value.startswith('\''): 28 | continue 29 | if Util.isUrlScheme(value) or value.find('.') == -1: 30 | continue 31 | 32 | url = '//' + Util.getSchemeFreeUrl(value) 33 | cdnbypass = Util.matchWildcardUrls(url, cdn) 34 | if cdnbypass: 35 | bypassDomain = '' 36 | bypassUrl = '//' + cdnbypass.netloc + cdnbypass.path 37 | bypassDomain = cdnbypass.netloc 38 | findings.append(Finding(csp.headerkey,FindingType.SCRIPT_WHITELIST_BYPASS,bypassDomain + ' is known to host many libraries which allow to bypass this CSP. Do not whitelist all scripts of a CDN.',FindingSeverity.HIGH, effectiveScriptSrcDirective, value)) 39 | 40 | return findings 41 | -------------------------------------------------------------------------------- /securityheaders/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .header import Header 2 | from .securityheader import SecurityHeader 3 | from .directive import Directive 4 | from .keyword import Keyword 5 | from . import annotations 6 | from .annotations import * 7 | from .modelfactory import ModelFactory 8 | from securityheaders import Util 9 | 10 | from .clearsitedata import * 11 | from .cors import CORSDirective 12 | from .cors.allowcredentials import * 13 | from .cors.allowheaders import * 14 | from .cors.allowmethods import * 15 | from .cors.alloworigin import * 16 | from .cors.exposeheaders import * 17 | from .cors.maxage import * 18 | from .csp import * 19 | from .hsts import * 20 | from .xcontenttypeoptions import * 21 | from .xframeoptions import * 22 | from .xxssprotection import * 23 | from .referrerpolicy import * 24 | from .featurepolicy import * 25 | from .server import * 26 | from .xpoweredby import * 27 | from .xwebkitcsp import * 28 | from .xcsp import * 29 | from .xdownloadoptions import * 30 | from .expectct import * 31 | from .xaspnetversion import * 32 | from .xaspnetmvcversion import * 33 | from .hpkp import * 34 | from .xpcdp import * 35 | from .setcookie import * 36 | 37 | __all__ = ['annotations','csp','cors','clearsitedata','hsts','xcontenttypeoptions','xframeoptions','xxssprotection','featurepolicy','referrerpolicy','server','xpoweredby', 'expectct','xcsp','xwebkitcsp','xpcdp','xaspnetversion','xaspnetmvcversion','hpkp','xdownloadoptions'] 38 | clazzes = list(Util.inheritors(Header)) 39 | clazzes.extend(Util.inheritors(Directive)) 40 | clazzes.extend(Util.inheritors(Keyword)) 41 | all_my_base_classes = {cls.__name__: cls for cls in clazzes} 42 | __all__.extend(all_my_base_classes) 43 | __all__.append('ModelFactory') 44 | __all__.append('CSPVersion') 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /securityheaders/checkers/unknowndirectivechecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import SyntaxChecker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class UnknownDirectiveChecker(SyntaxChecker): 5 | 6 | def check(self, headers, opt_options=dict()): 7 | headernames = ModelFactory().getheadernames() 8 | 9 | findings = [] 10 | for header in headernames: 11 | hdr = ModelFactory().getheader(header) 12 | try: 13 | obj = self.extractheader(headers, hdr) 14 | if obj and obj.parsedstring: 15 | findings.extend(self.mycheck(obj)) 16 | except: 17 | pass 18 | return findings 19 | 20 | def mycheck(self, data): 21 | findings = [] 22 | 23 | if not data: 24 | return findings 25 | 26 | directiveclazz = data.directive 27 | if not hasattr(directiveclazz,'anydirective') or (hasattr(directiveclazz,'anydirective') and not directiveclazz.anydirective): 28 | seperator = directiveclazz.directivevalueseperator() 29 | for directive in data.keys(): 30 | if not directiveclazz.isDirective(directive): 31 | if directive.endswith(seperator): 32 | findings.append(Finding(data.headerkey, FindingType.UNKNOWN_DIRECTIVE,str(data.headerkey) + " directives don't end with a " + str(seperator),FindingSeverity.SYNTAX, None, directive)) 33 | else: 34 | findings.append(Finding(data.headerkey, FindingType.UNKNOWN_DIRECTIVE,'Directive "' + str(directive) + '" is not a known ' + str(data.headerkey) + ' directive.',FindingSeverity.SYNTAX,directive)) 35 | 36 | return findings 37 | -------------------------------------------------------------------------------- /securityheaders/checkers/finding.py: -------------------------------------------------------------------------------- 1 | class Finding(object): 2 | def __init__(self, header, ftype, description, severity, opt_directive=None, opt_value=None, opt_url = None, opt_urlid=None): 3 | """ Constructor for a finding object. 4 | 5 | Args: 6 | header (str): the header for which this finding is valid 7 | ftype (FindingType): the type of finding 8 | description (str): the description of the finding 9 | severity (FindingSeverity): the severity of the finding 10 | opt_directive (Directive): if a header value has multiple keywords, then this is the keyword it was valid for 11 | opt_value (str): the insecure value 12 | """ 13 | 14 | self.header = header 15 | self.ftype = ftype 16 | self.description = description 17 | self.severity = severity 18 | self.directive = opt_directive 19 | self.value = opt_value 20 | self.url = opt_url 21 | self.urlid = opt_urlid 22 | 23 | def __eq__(self, other): 24 | if not other: 25 | return False 26 | 27 | if not isinstance(other, self.__class__): 28 | return False 29 | 30 | return self.header == other.header and self.ftype == other.ftype and self.description == other.description and self.severity == other.severity and self.directive == other.directive and self.value == other.value 31 | 32 | 33 | def __ne__(self, other): 34 | return not self.__eq__(other) 35 | 36 | def __str__(self): 37 | """ Returns a string representation of this finding 38 | """ 39 | return str(self.header) +"\t" + str(self.ftype) +"\t" + str(self.description) +"\t" + str(self.directive) + "\t" + str(self.value) 40 | 41 | def __repr__(self): 42 | return self.__str__() 43 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspxframeopts.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import FindingSeverity, Finding, FindingType 2 | from securityheaders.models import XFrameOptions, CSP 3 | 4 | from securityheaders.checkers.xframeoptions import XFrameOptionsChecker 5 | from securityheaders.checkers.csp import CSPChecker 6 | 7 | class CSPXFrameOptionsInconsistentChecker(XFrameOptionsChecker, CSPChecker): 8 | def check(self, headers, opt_options=dict()): 9 | opts = self.getxframeoptions(headers) 10 | csp = self.getcsp(headers) 11 | if not opts or not csp: 12 | return [] 13 | 14 | value = None 15 | if csp.directive.FRAME_ANCESTORS in csp.keys(): 16 | value = csp[csp.directive.FRAME_ANCESTORS] 17 | if not value: 18 | return [] 19 | 20 | inconsistent = False 21 | if opts.deny() and not self.__notcontains_keyword__(value, csp.keyword.NONE): 22 | inconsistent = True 23 | elif opts.sameorigin() and not self.__notcontains_keyword__(value, csp.keyword.SELF): 24 | inconsistent = True 25 | elif opts.allowfrom() not in value: 26 | inconsistent = True 27 | 28 | if inconsistent: 29 | return [Finding(CSP.headerkey, FindingType.INCONSISTENCIES, 'The X-Frame-Options and the Content-Security-Policy have different framing policies. The Content-Security-Policy header had a ' + str(csp.directive.FRAME_ANCESTORS) + ' directive with as value ' + ", ".join(value) + ', while the X-Frame-Options header had as value "' + str(opts.keys()[0]) + '". Browsers should follow the CSP, but that behavior is not guaranteed.' ,FindingSeverity.INFO, csp.directive.FRAME_ANCESTORS, value)] 30 | return [] 31 | 32 | def __notcontains_keyword__(self, value, keyword): 33 | return keyword != value and keyword.value not in str(value) 34 | 35 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_ipsource.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | try: 6 | from urlparse import urlparse 7 | except ModuleNotFoundError: 8 | from urllib.parse import urlparse #python3 9 | 10 | import ipaddress 11 | 12 | class CSPCheckIPSource(CSPCheck): 13 | def __init__(self, csp, function): 14 | self.csp = csp 15 | self.function = function 16 | 17 | def check(self): 18 | csp = self.csp 19 | if not csp: 20 | return [] 21 | 22 | findings = [] 23 | 24 | self.function(csp.parsedstring, self.checkIP, findings) 25 | return findings 26 | 27 | def checkIP(self, directive, directiveValues, findings): 28 | csp = self.csp 29 | for value in directiveValues: 30 | url = '//' + Util.getSchemeFreeUrl(value) 31 | host = urlparse(url).netloc 32 | ip = None 33 | validip = True 34 | 35 | try: 36 | ip = ipaddress.ip_address(u''+host) 37 | except ValueError: 38 | validip = False 39 | if validip: 40 | ipString = str(ip) + '' 41 | 42 | if '127.0.0.1' in ipString: 43 | findings.append(Finding(csp.headerkey,FindingType.IP_SOURCE, directive.value + ' directive allows localhost as source. Please make sure to remove this in production environments.',FindingSeverity.INFO, directive, value)) 44 | else: 45 | findings.append(Finding(csp.headerkey,FindingType.IP_SOURCE, directive.value + ' directive has an IP-Address as source: ' + ipString + ' (will be ignored by browsers!). ', FindingSeverity.INFO, directive, value)) 46 | 47 | 48 | -------------------------------------------------------------------------------- /securityheaders/checkers/expectct/test_notenforce.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.expectct import ExpectCTNotEnforcedChecker 4 | 5 | class ExpectCTNotEnforcedCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = ExpectCTNotEnforcedChecker() 8 | 9 | def test_check(self): 10 | xempty = dict() 11 | self.assertEqual(self.x.check(xempty), []) 12 | 13 | def test_checkNone(self): 14 | xnone = None 15 | self.assertEqual(self.x.check(xnone), []) 16 | 17 | def test_Good(self): 18 | xhas = dict() 19 | xhas['expect-ct'] = 'max-age=500000, enforce, report-uri=https://google.com' 20 | self.assertEqual(self.x.check(xhas), []) 21 | 22 | def test_Bad(self): 23 | xhasbad = dict() 24 | xhasbad['expect-ct'] = 'max-age=20000, report-uri=https://google.com' 25 | result = self.x.check(xhasbad) 26 | self.assertIsNotNone(result) 27 | self.assertEqual(len(result), 1) 28 | 29 | def test_Bad2(self): 30 | xhasbad = dict() 31 | xhasbad['expect-ct'] = 'max-age=0, enforce, report-uri=https://google.com' 32 | result = self.x.check(xhasbad) 33 | self.assertIsNotNone(result) 34 | self.assertEqual(len(result), 1) 35 | 36 | def test_Bad3(self): 37 | xhasbad = dict() 38 | xhasbad['expect-ct'] = 'max-age=0, report-uri=https://google.com' 39 | result = self.x.check(xhasbad) 40 | self.assertIsNotNone(result) 41 | self.assertEqual(len(result), 2) 42 | 43 | def test_Bad4(self): 44 | xhasbad = dict() 45 | xhasbad['expect-ct'] = 'max-age=10, enforce, report-uri=https://google.com' 46 | result = self.x.check(xhasbad) 47 | self.assertIsNotNone(result) 48 | self.assertEqual(len(result), 1) 49 | 50 | if __name__ == '__main__': 51 | unittest.main() 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #project 2 | tmp/ 3 | report/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | bin/ 16 | include/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | 112 | ### Python Patch ### 113 | .venv/ 114 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_deprecateddirective.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.csp import CSPDeprecatedDirectiveChecker, CSPReportOnlyDeprecatedDirectiveChecker 4 | 5 | class DeprectedDirectiveTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = CSPDeprecatedDirectiveChecker() 8 | self.y = CSPReportOnlyDeprecatedDirectiveChecker() 9 | 10 | 11 | def test_checkNoCSP(self): 12 | nox = dict() 13 | nox['test'] = 'value' 14 | self.assertEqual(self.x.check(nox), []) 15 | 16 | def test_checkNone(self): 17 | nonex = None 18 | self.assertEqual(self.x.check(nonex), []) 19 | 20 | def test_NonePolicy(self): 21 | hasx = dict() 22 | hasx['content-security-policy'] = None 23 | self.assertEqual(self.x.check(hasx), []) 24 | 25 | def test_DeprecatedReportUriCSP3(self): 26 | hasx3 = dict() 27 | hasx3['content-security-policy'] = "report-uri http://foo.bar/csp" 28 | result = self.x.check(hasx3) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_RODeprecatedReportUriCSP3(self): 33 | hasx3 = dict() 34 | hasx3['content-security-policy-report-only'] = "report-uri http://foo.bar/csp" 35 | result = self.y.check(hasx3) 36 | self.assertIsNotNone(result) 37 | self.assertEqual(len(result), 1) 38 | 39 | def test_ValidCSP(self): 40 | hasx2 = dict() 41 | hasx2['content-security-policy'] = "default-src 'self'; script-src 'nonce-4AEemGb0xJptoIGFP3Nd'" 42 | self.assertEqual(self.x.check(hasx2), []) 43 | 44 | def test_ROValidCSP(self): 45 | hasx2 = dict() 46 | hasx2['content-security-policy-report-only'] = "default-src 'self'; script-src 'nonce-4AEemGb0xJptoIGFP3Nd'" 47 | self.assertEqual(self.y.check(hasx2), []) 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | -------------------------------------------------------------------------------- /securityheaders/models/modelfactory.py: -------------------------------------------------------------------------------- 1 | import securityheaders 2 | from securityheaders import Util 3 | from securityheaders.models import Header, Directive, Keyword 4 | from securityheaders.singleton import Singleton 5 | 6 | import warnings 7 | 8 | class ModelFactory(Singleton): 9 | def __init__(self): 10 | self.clazzes = dict() 11 | self.headers = dict() 12 | 13 | def getmodel(self,modelname): 14 | if(len(self.clazzes.keys()) == 0): 15 | self.populate() 16 | if modelname in self.clazzes.keys(): 17 | return self.clazzes[modelname]() 18 | return None 19 | 20 | def getheadernames(self): 21 | if(len(self.clazzes.keys()) == 0): 22 | self.populate() 23 | return set(self.headers.keys()) 24 | 25 | def getheader(self, name): 26 | if(len(self.headers.keys()) == 0): 27 | self.populate() 28 | if name in self.headers.keys(): 29 | return self.headers[name] 30 | return None 31 | 32 | def getnames(self): 33 | if(len(self.clazzes.keys()) == 0): 34 | self.populate() 35 | return self.clazzes.keys() 36 | 37 | 38 | def populate(self): 39 | # path = securityheaders.models.__path__[0] 40 | # with warnings.catch_warnings(): 41 | # warnings.simplefilter("ignore") 42 | # Util.load_all_modules_from_dir(path) 43 | clazzes = list(Util.inheritors(Header)) 44 | clazzes.extend(Util.inheritors(Directive)) 45 | clazzes.extend(Util.inheritors(Keyword)) 46 | for header in list(Util.inheritors(Header)): 47 | if hasattr(header,'headerkey'): 48 | self.headers[header.headerkey] = header 49 | 50 | all_my_base_classes = {cls: cls for cls in clazzes} 51 | for clazz in all_my_base_classes: 52 | self.clazzes[clazz.__name__] = clazz 53 | 54 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_srchttp.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | from securityheaders.checkers.csp import CSPSCRHTTPChecker, CSPReportOnlySCRHTTPChecker 5 | 6 | class HTTPTest(unittest.TestCase): 7 | def setUp(self): 8 | self.x = CSPSCRHTTPChecker() 9 | self.y = CSPReportOnlySCRHTTPChecker() 10 | 11 | def test_checkNoCSP(self): 12 | nox = dict() 13 | nox['test'] = 'value' 14 | self.assertEqual(self.x.check(nox), []) 15 | 16 | def test_checkNone(self): 17 | nonex = None 18 | self.assertEqual(self.x.check(nonex), []) 19 | 20 | def test_NoneCSP(self): 21 | hasx = dict() 22 | hasx['content-security-policy'] = None 23 | self.assertEqual(self.x.check(hasx), []) 24 | 25 | def test_HTTPURI(self): 26 | hasx3 = dict() 27 | hasx3['content-security-policy'] = "report-uri http://foo.bar/csp" 28 | result = self.x.check(hasx3) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_HTTPSURI(self): 33 | hasx4 = dict() 34 | hasx4['content-security-policy'] = "report-uri https://foo.bar/csp" 35 | self.assertEqual(self.x.check(hasx4), []) 36 | 37 | def test_HTTPURIRO(self): 38 | hasx3 = dict() 39 | hasx3['content-security-policy-report-only'] = "report-uri http://foo.bar/csp" 40 | result = self.y.check(hasx3) 41 | self.assertIsNotNone(result) 42 | self.assertEqual(len(result), 1) 43 | 44 | def test_HTTPSURIRO(self): 45 | hasx4 = dict() 46 | hasx4['content-security-policy-report-only'] = "report-uri https://foo.bar/csp" 47 | self.assertEqual(self.y.check(hasx4), []) 48 | 49 | def test_NoURI(self): 50 | hasx2 = dict() 51 | hasx2['content-security-policy'] = "default-src 'self'; script-src 'nonce-4AEemGb0xJptoIGFP3Nd'" 52 | self.assertEqual(self.x.check(hasx2), []) 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /securityheaders/checkers/findingtype.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class FindingType(Enum): 4 | #INFO 5 | INFO_HEADER = 1 #used for collecting raw headers 6 | INFO_DIRECTIVE = 2 #used for recording directive info 7 | INFO_URL = 3 8 | 9 | #syntax issues 10 | MISSING_SEMICOLON = 100 11 | UNKNOWN_DIRECTIVE = 101 12 | INVALID_KEYWORD = 102 13 | IGNORED = 405 14 | DEPRECATED_DIRECTIVE = 309 15 | MISSING_DIRECTIVES = 300 16 | MISSING_VALUES = 299 17 | 18 | 19 | #generic issues 20 | MISSING_HEADER = 103 21 | INSECURE_HEADER = 104 22 | INFO_DISCLOSURE = 105 23 | DEPRECATED_HEADER = 106 24 | INCONSISTENCIES = 107 25 | 26 | SRC_HTTP = 310 27 | 28 | #CSP-specific issues 29 | SCRIPT_UNSAFE_INLINE = 301 30 | SCRIPT_UNSAFE_EVAL = 302 31 | PLAIN_URL_SCHEMES = 303 32 | PLAIN_WILDCARD = 304 33 | SCRIPT_WHITELIST_BYPASS = 305 34 | OBJECT_WHITELIST_BYPASS = 306 35 | NONCE_LENGTH = 307 36 | IP_SOURCE = 308 37 | 38 | 39 | STRICT_DYNAMIC = 400 40 | STRICT_DYNAMIC_NOT_STANDALONE = 401 41 | NONCE_HASH = 402 42 | UNSAFE_INLINE_FALLBACK = 403 43 | WHITELIST_FALLBACK = 404 44 | 45 | REPORT_ONLY=410 46 | 47 | #CORS issues 48 | STAR_ORIGIN = 500 49 | NULL_ORIGIN = 501 50 | HTTP_ORIGIN = 502 51 | MAX_AGE_TOO_LONG = 503 52 | SENSITIVE_HEADER_EXPOSED = 504 53 | 54 | #XXSSProtection 55 | DISABLE_XSS_FILTER = 600 56 | HTTP_REPORT = 601 57 | 58 | 59 | ALLOW_FROM_EMPTY = 700 60 | ALLOW_FROM = 701 61 | 62 | #HSTS 63 | NO_SUBDOMAINS = 800 64 | MAX_AGE_ZERO = 801 65 | 66 | NOSNIFF = 900 67 | 68 | #REFERRERPOLICY 69 | UNSAFE_URL=950 70 | ORIGIN_WHEN_CROSS_ORIGIN=951 71 | 72 | #EXPECT_CT 73 | NOT_ENFORCED = 970 74 | 75 | #ERROR 76 | ERROR = 1000 77 | 78 | def __str__(self): 79 | """ Returns a string representaiton of this finding type 80 | """ 81 | return str(self.name.lower()) 82 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_noncelength.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 4 | from securityheaders import Util 5 | from .cspcheck import CSPCheck 6 | 7 | 8 | class CSPCheckNonceLength(CSPCheck): 9 | def __init__(self, csp, function): 10 | self.csp = csp 11 | self.function = function 12 | 13 | def check(self, opt_options=dict()): 14 | csp = self.csp 15 | if not csp: 16 | return [] 17 | 18 | findings = [] 19 | 20 | self.function(csp.parsedstring, self.checkNonce, findings) 21 | return findings 22 | 23 | 24 | def checkNonce(self, directive, directiveValues, findings): 25 | nonce_pattern = re.compile("^'nonce-(.+)'$") 26 | for value in directiveValues: 27 | value = str(value) 28 | match = nonce_pattern.search(value) 29 | #nonce value starts with nonce- but does not have an actual value 30 | if not bool(match) and "nonce-" in value: 31 | findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should be at least 8 characters long.',FindingSeverity.MEDIUM, directive, value)) 32 | #no nonce value in the directive value 33 | if not bool(match): 34 | continue 35 | #nonce value in the directive value 36 | else: 37 | #get the value of the nonce; i.e. everything after nonce- 38 | nonceValue = match.group(1) 39 | if len(nonceValue) < 8: 40 | findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should be at least 8 characters long.',FindingSeverity.MEDIUM, directive, value)) 41 | if not self.csp.isNonce(value, True): 42 | findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should only use the base64 charset.',FindingSeverity.INFO, directive, value)) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_flash.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckFlash(CSPCheck): 6 | def __init__(self, csp): 7 | self.csp = csp 8 | 9 | def check(self, opt_options=dict()): 10 | csp = self.csp 11 | if not csp: 12 | return [] 13 | 14 | findings = [] 15 | angular = [] 16 | jsonp = [] 17 | jsonpeval = [] 18 | if 'bypasses' not in opt_options.keys(): 19 | bypasses = [] 20 | else: 21 | bypasses = opt_options['bypasses'] 22 | 23 | directive = csp.getEffectiveDirective(csp.directive.OBJECT_SRC) 24 | objectSrcValues = [] 25 | try: 26 | objectSrcValues = csp[directive] 27 | except KeyError: 28 | objectSrcValues = [] 29 | if csp.directive.PLUGIN_TYPES in csp.parsedstring: 30 | pluginTypes = csp[csp.directive.PLUGIN_TYPES] 31 | else: 32 | pluginTypes = None 33 | 34 | if pluginTypes and not 'application/x-shockwave-flash' in pluginTypes: 35 | return [] 36 | 37 | for value in objectSrcValues: 38 | if value == csp.keyword.NONE: 39 | return [] 40 | 41 | url = '//' + Util.getSchemeFreeUrl(value) 42 | flashBypass = Util.matchWildcardUrls(url, bypasses) 43 | if (flashBypass): 44 | findings.append(Finding(csp.headerkey, FindingType.OBJECT_WHITELIST_BYPASS, flashBypass.netloc + ' is known to host Flash files which allow to bypass this CSP.',FindingSeverity.HIGH, directive, value)) 45 | elif (directive == csp.directive.OBJECT_SRC): 46 | findings.append(Finding(csp.headerkey, FindingType.OBJECT_WHITELIST_BYPASS, 'Can you restrict object-src to \'none\' only?',FindingSeverity.MEDIUM_MAYBE, directive,value)) 47 | 48 | return findings 49 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/checker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Checker 2 | from securityheaders.models.csp import CSP, CSPDirective, CSPVersion 3 | 4 | class CSPChecker(Checker): 5 | 6 | def myoptions(cls): 7 | return {'CSPversion':CSPVersion} 8 | 9 | def getcsp(self, headers): 10 | return self.extractheader(headers, CSP) 11 | 12 | 13 | def extractEffectiveCSP(self, headers, opt_options=[]): 14 | csp = self.getcsp(headers) 15 | me = self.__class__.__name__ 16 | if not csp: 17 | return None 18 | if me in opt_options.keys() and 'CSPversion' in opt_options[me].keys(): 19 | version = opt_options[me]['CSPversion'] 20 | else: 21 | version = CSPVersion.CSP3 22 | return csp.getEffectiveCsp(version) 23 | 24 | 25 | def extractEffectiveDirective(self, headers, directive, options): 26 | csp = self.extractEffectiveCSP(headers, options) 27 | if not csp: 28 | return None 29 | return csp.getEffectiveDirective(CSPDirective.SCRIPT_SRC) 30 | 31 | 32 | 33 | def effectiveDirectiveValues(self, headers, actualdirective, options): 34 | csp = self.extractEffectiveCSP(headers, options) 35 | if not csp: 36 | return [] 37 | directive = csp.getEffectiveDirective(actualdirective) 38 | if not directive: 39 | return [] 40 | 41 | values = [] 42 | try: 43 | values = csp[directive] 44 | except: 45 | values = [] 46 | return values 47 | 48 | 49 | def applyCheckFunktionToDirectives(self, parsedCsp, check, findings, opt_directives=[]): 50 | directiveNames = [] 51 | if parsedCsp: 52 | directiveNames = parsedCsp.keys() 53 | if opt_directives: 54 | directiveNames = opt_directives 55 | for directive in directiveNames: 56 | directiveValues = parsedCsp[directive] 57 | if directiveValues: 58 | check(directive, directiveValues, findings) 59 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_plainurlschemes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.csp import CSPPlainUrlSchemesChecker, CSPReportOnlyPlainUrlSchemesChecker 4 | 5 | 6 | class UnsafeUrkSchemeTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.x = CSPPlainUrlSchemesChecker() 10 | self.y = CSPReportOnlyPlainUrlSchemesChecker() 11 | 12 | 13 | def test_checkNoCSP(self): 14 | nox = dict() 15 | nox['test'] = 'value' 16 | self.assertEqual(self.x.check(nox), []) 17 | 18 | def test_checkNone(self): 19 | nonex = None 20 | self.assertEqual(self.x.check(nonex), []) 21 | 22 | def test_checkNoneCSP(self): 23 | hasx = dict() 24 | hasx['content-security-policy'] = None 25 | self.assertEqual(self.x.check(hasx), []) 26 | 27 | 28 | def test_All(self): 29 | hasx4 = dict() 30 | hasx4['content-security-policy'] = "script-src https: http: data:" 31 | self.assertIsNotNone(self.x.check(hasx4)) 32 | self.assertEqual(len(self.x.check(hasx4)), 3) #all 3 of them 33 | 34 | def test_http(self): 35 | hasx3 = dict() 36 | hasx3['content-security-policy'] = "script-src http:" 37 | self.assertIsNotNone(self.x.check(hasx3)) 38 | self.assertEqual(len(self.x.check(hasx3)), 1) #http: 39 | 40 | def test_validCSP(self): 41 | hasx2 = dict() 42 | hasx2['content-security-policy'] = "default-src 'self'; script-src tweakers.net" 43 | self.assertEqual(self.x.check(hasx2), []) 44 | 45 | def test_httpro(self): 46 | hasx3 = dict() 47 | hasx3['content-security-policy-report-only'] = "script-src http:" 48 | self.assertIsNotNone(self.y.check(hasx3)) 49 | self.assertEqual(len(self.y.check(hasx3)), 1) #http: 50 | 51 | def test_validCSPro(self): 52 | hasx2 = dict() 53 | hasx2['content-security-policy-report-only'] = "default-src 'self'; script-src tweakers.net" 54 | self.assertEqual(self.y.check(hasx2), []) 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /securityheaders/checkers/cors/exposeheaders/test_exposesensitiveheaders.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.cors import AccessControlExposeHeadersSensitiveChecker 4 | 5 | class AccessControlExposeHeadersSensitiveCheckerTest(unittest.TestCase): 6 | def setUp(self): 7 | self.x = AccessControlExposeHeadersSensitiveChecker() 8 | 9 | def test_checkNoHeader(self): 10 | nox = dict() 11 | nox['test'] = 'value' 12 | self.assertEqual(self.x.check(nox), []) 13 | 14 | def test_checkNone(self): 15 | nonex = None 16 | self.assertEqual(self.x.check(nonex), []) 17 | 18 | def test_checkNone2(self): 19 | hasx = dict() 20 | hasx['access-control-expose-headers'] = None 21 | self.assertEqual(self.x.check(hasx), []) 22 | 23 | def test_checkInvalid(self): 24 | hasx2 = dict() 25 | hasx2['access-control-expose-headers'] = "Authentication-Token" 26 | result = self.x.check(hasx2) 27 | self.assertIsNotNone(result) 28 | self.assertEqual(len(result), 1) 29 | 30 | def test_checkInvalid2(self): 31 | hasx5 = dict() 32 | hasx5['access-control-expose-headers'] = "Authorization" 33 | result = self.x.check(hasx5) 34 | self.assertIsNotNone(result) 35 | self.assertEqual(len(result), 1) 36 | 37 | def test_checkInvalid3(self): 38 | hasx5 = dict() 39 | hasx5['access-control-expose-headers'] = "Session" 40 | result = self.x.check(hasx5) 41 | self.assertIsNotNone(result) 42 | self.assertEqual(len(result), 1) 43 | 44 | def test_checkInvalid4(self): 45 | hasx5 = dict() 46 | hasx5['access-control-expose-headers'] = "Session, Authentication-Token, PUT" 47 | result = self.x.check(hasx5) 48 | self.assertIsNotNone(result) 49 | self.assertEqual(len(result), 2) 50 | 51 | def test_checkValid2(self): 52 | hasx5 = dict() 53 | hasx5['access-control-expose-headers'] = "PUT" 54 | self.assertEqual(self.x.check(hasx5), []) 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_noncelength.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from securityheaders.checkers.csp import CSPNonceLengthChecker, CSPReportOnlyNonceLengthChecker 4 | 5 | class NonceLengthTest(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.x = CSPNonceLengthChecker() 9 | self.y = CSPReportOnlyNonceLengthChecker() 10 | 11 | def test_checkNoCSP(self): 12 | nox = dict() 13 | nox['test'] = 'value' 14 | self.assertEqual(self.x.check(nox), []) 15 | 16 | def test_checkNone(self): 17 | nonex = None 18 | self.assertEqual(self.x.check(nonex), []) 19 | 20 | def test_NoneCSP(self): 21 | hasx = dict() 22 | hasx['content-security-policy'] = None 23 | self.assertEqual(self.x.check(hasx), []) 24 | 25 | def test_ShortNonce(self): 26 | hasx3 = dict() 27 | hasx3['content-security-policy'] = "script-src 'nonce-short'" 28 | result = self.x.check(hasx3) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_ShortNonce2(self): 33 | hasx4 = dict() 34 | hasx4['content-security-policy'] = "default-src 'self'; script-src 'nonce-'" 35 | result = self.x.check(hasx4) 36 | self.assertIsNotNone(result) 37 | self.assertEqual(len(result), 1) 38 | 39 | def test_ShortNonce2RO(self): 40 | hasx4 = dict() 41 | hasx4['content-security-policy-report-only'] = "default-src 'self'; script-src 'nonce-'" 42 | result = self.y.check(hasx4) 43 | self.assertIsNotNone(result) 44 | self.assertEqual(len(result), 1) 45 | 46 | def test_ValidNonce(self): 47 | hasx2 = dict() 48 | hasx2['content-security-policy'] = "default-src 'self'; script-src 'nonce-4AEemGb0xJptoIGFP3Nd'" 49 | self.assertEqual(self.x.check(hasx2), []) 50 | 51 | def test_ValidNonceRO(self): 52 | hasx2 = dict() 53 | hasx2['content-security-policy-report-only'] = "default-src 'self'; script-src 'nonce-4AEemGb0xJptoIGFP3Nd'" 54 | self.assertEqual(self.y.check(hasx2), []) 55 | 56 | 57 | if __name__ == '__main__': 58 | unittest.main() 59 | 60 | -------------------------------------------------------------------------------- /securityheaders/test_util.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | try: 3 | from urlparse import urlparse 4 | except ModuleNotFoundError: 5 | from urllib.parse import urlparse #python3 6 | 7 | from securityheaders import Util 8 | 9 | class UtilTest(unittest.TestCase): 10 | def test_isUrlSchemeNone(self): 11 | self.assertFalse(Util.isUrlScheme(None)) 12 | 13 | def test_isUrlSchemeHttp(self): 14 | self.assertTrue(Util.isUrlScheme('http:')) 15 | 16 | def test_isUrlSchemeHttps(self): 17 | self.assertTrue(Util.isUrlScheme('https:')) 18 | 19 | def test_isUrlSchemeInvalid(self): 20 | self.assertFalse(Util.isUrlScheme('noscheme')) 21 | 22 | def test_getSchemeFreeUrlValid(self): 23 | self.assertEqual(Util.getSchemeFreeUrl('http://www.synopsys.com'), 'www.synopsys.com') 24 | 25 | def test_getSchemeFreeUrlCapitals(self): 26 | self.assertEqual(Util.getSchemeFreeUrl('HTTPS://www.synopsys.com'), 'www.synopsys.com') 27 | 28 | def test_getSchemeFreeUrlNone(self): 29 | self.assertEqual(Util.getSchemeFreeUrl(None), None) 30 | 31 | def test_matchWildcardUrls(self): 32 | urls = [ 33 | '//vk.com/swf/video.swf', 34 | '//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf' 35 | 36 | ] 37 | self.assertEqual(Util.matchWildcardUrls('https://*.vk.com', urls), None) 38 | self.assertEqual(Util.matchWildcardUrls('https://ajax.googleapis.com', urls), urlparse('//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf')) 39 | self.assertEqual(Util.matchWildcardUrls('https://*.googleapis.com', urls), urlparse('//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf')) 40 | self.assertEqual(Util.matchWildcardUrls(None, urls), None) 41 | self.assertEqual(Util.matchWildcardUrls('https://*.googleapis.com', None), None) 42 | self.assertEqual(Util.matchWildcardUrls('https://*.googleapis.com', []), None) 43 | self.assertEqual(Util.matchWildcardUrls('*.googleapis.com', urls), urlparse('//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf')) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_ipsourcechecker.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | from securityheaders.checkers.csp import CSPIPSourceChecker, CSPReportOnlyIPSourceChecker 5 | 6 | 7 | class IPSourceTest(unittest.TestCase): 8 | def setUp(self): 9 | self.x = CSPIPSourceChecker() 10 | self.y = CSPReportOnlyIPSourceChecker() 11 | 12 | def test_checkNoCSP(self): 13 | nox = dict() 14 | nox['test'] = 'value' 15 | self.assertEqual(self.x.check(nox), []) 16 | 17 | def test_checkNone(self): 18 | nonex = None 19 | self.assertEqual(self.x.check(nonex), []) 20 | 21 | def test_NoneCsp(self): 22 | hasx = dict() 23 | hasx['content-security-policy'] = None 24 | self.assertEqual(self.x.check(hasx), []) 25 | 26 | def test_IpSource(self): 27 | hasx5 = dict() 28 | hasx5['content-security-policy'] = 'script-src 127.0.0.1' 29 | result = self.x.check(hasx5) 30 | self.assertIsNotNone(result) 31 | self.assertEqual(len(result), 1) 32 | 33 | def test_NoneCspRO(self): 34 | hasx = dict() 35 | hasx['content-security-policy-report-only'] = None 36 | self.assertEqual(self.y.check(hasx), []) 37 | 38 | def test_IpSourceRO(self): 39 | hasx5 = dict() 40 | hasx5['content-security-policy-report-only'] = 'script-src 127.0.0.1' 41 | result = self.y.check(hasx5) 42 | self.assertIsNotNone(result) 43 | self.assertEqual(len(result), 1) 44 | 45 | def test_IpSource2(self): 46 | hasx4 = dict() 47 | hasx4['content-security-policy'] = 'script-src 200.200.200.200' 48 | result = self.x.check(hasx4) 49 | self.assertIsNotNone(result) 50 | self.assertEqual(len(result), 1) 51 | 52 | def test_URI(self): 53 | hasx3 = dict() 54 | hasx3['content-security-policy'] = "report-uri http://foo.bar/csp" 55 | self.assertEqual(self.x.check(hasx3), []) 56 | 57 | def test_URI2(self): 58 | hasx2 = dict() 59 | hasx2['content-security-policy'] = "default-src 'self'; script-src tweakers.net" 60 | self.assertEqual(self.x.check(hasx2), []) 61 | 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /securityheaders/models/csp/cspdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import Directive 2 | from securityheaders.models.annotations import requireddirectives, requireddirectivevalues 3 | 4 | @requireddirectivevalues('form-action','frame-ancestors','report-uri','report-to','require-sri-for','plugin-types','worker-src','style-src','object-src','manifest-src','frame-src','default-src','connect-src','child-src') 5 | class CSPDirective(Directive): 6 | # Fetch directives 7 | CHILD_SRC = 'child-src', 'childSrc' 8 | CONNECT_SRC = 'connect-src', 'connectSrc' 9 | DEFAULT_SRC = 'default-src', 'defaultSrc' 10 | FONT_SRC = 'font-src', 'fontSrc' 11 | FRAME_SRC = 'frame-src', 'frameSrc' 12 | IMG_SRC = 'img-src', 'imgSrc' 13 | MEDIA_SRC = 'media-src', 'mediaSrc' 14 | OBJECT_SRC = 'object-src', 'objectSrc' 15 | SCRIPT_SRC = 'script-src', 'scriptSrc' 16 | STYLE_SRC = 'style-src', 'styleSrc' 17 | 18 | MANIFEST_SRC = 'manifest-src', 'manifestSrc' 19 | WORKER_SRC = 'worker-src', 'workerSrc' 20 | 21 | # Document directives 22 | BASE_URI = 'base-uri','baseUri' 23 | PLUGIN_TYPES = 'plugin-types','pluginTypes' 24 | SANDBOX = 'sandbox','sandBox' 25 | DISOWN_OPENER = 'disown-opener','disownOpener' 26 | 27 | # Navigation directives 28 | FORM_ACTION = 'form-action','formAction' 29 | FRAME_ANCESTORS = 'frame-ancestors','frameAncestors' 30 | 31 | # Reporting directives 32 | REPORT_TO = 'report-to','reportTo' 33 | REPORT_URI = 'report-uri','reportUri' 34 | 35 | # Other directives 36 | BLOCK_ALL_MIXED_CONTENT = 'block-all-mixed-content','blockAllMixedContent' 37 | UPGRADE_INSECURE_REQUESTS = 'upgrade-insecure-requests','upgradeInsecureRequests' 38 | REFLECTED_XSS = 'reflected-xss','reflectedXss' 39 | REFERRER = 'referrer' 40 | REQUIRE_SRI_FOR = 'require-sri-for','requireSriFor' 41 | 42 | 43 | @classmethod 44 | def isDirective(cls, directive): 45 | """ Checks whether a given string is a directive 46 | 47 | Args: 48 | directive (str): the string to validate 49 | """ 50 | if isinstance(directive, CSPDirective): 51 | return True 52 | return any(directive.lower() == item for item in list(cls.keys())) 53 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/test_frameancestors.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | from securityheaders.checkers.csp import CSPFrameAncestorsChecker, CSPReportOnlyFrameAncestorsChecker 5 | 6 | class HTTPTest(unittest.TestCase): 7 | def setUp(self): 8 | self.x = CSPFrameAncestorsChecker() 9 | self.y = CSPReportOnlyFrameAncestorsChecker() 10 | 11 | def test_checkNoCSP(self): 12 | nox = dict() 13 | nox['test'] = 'value' 14 | self.assertEqual(self.x.check(nox), []) 15 | 16 | def test_checkNone(self): 17 | nonex = None 18 | self.assertEqual(self.x.check(nonex), []) 19 | 20 | def test_NoneCSP(self): 21 | hasx = dict() 22 | hasx['content-security-policy'] = None 23 | self.assertEqual(self.x.check(hasx), []) 24 | 25 | def test_FrameSrc(self): 26 | hasx3 = dict() 27 | hasx3['content-security-policy'] = "frame-ancestors https://foo.bar" 28 | result = self.x.check(hasx3) 29 | self.assertIsNotNone(result) 30 | self.assertEqual(len(result), 1) 31 | 32 | def test_Good(self): 33 | hasx4 = dict() 34 | hasx4['content-security-policy'] = "frame-ancestors 'self'" 35 | self.assertEqual(self.x.check(hasx4), []) 36 | 37 | def test_Good2(self): 38 | hasx3 = dict() 39 | hasx3['content-security-policy'] = "frame-ancestors 'none'" 40 | self.assertEqual(self.x.check(hasx3), []) 41 | 42 | def test_Bad2(self): 43 | hasx4 = dict() 44 | hasx4['content-security-policy'] = "frame-ancestors *.tweakers.net *.tweakimg.net" 45 | result = self.x.check(hasx4) 46 | self.assertIsNotNone(result) 47 | self.assertEqual(len(result), 2) 48 | 49 | 50 | def test_Good2RO(self): 51 | hasx3 = dict() 52 | hasx3['content-security-policy-report-only'] = "frame-ancestors 'none'" 53 | self.assertEqual(self.y.check(hasx3), []) 54 | 55 | def test_Bad2RO(self): 56 | hasx4 = dict() 57 | hasx4['content-security-policy-report-only'] = "frame-ancestors *.tweakers.net *.tweakimg.net" 58 | result = self.y.check(hasx4) 59 | self.assertIsNotNone(result) 60 | self.assertEqual(len(result), 2) 61 | 62 | if __name__ == '__main__': 63 | unittest.main() 64 | -------------------------------------------------------------------------------- /securityheaders/models/referrerpolicy/referrerpolicy.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.referrerpolicy import ReferrerPolicyDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('It is a new header that allows a site to control how much information the browser includes with navigations away from a document and should be set by all sites.') 7 | @headername('referrer-policy') 8 | @headerref('https://www.w3.org/TR/referrer-policy/') 9 | class ReferrerPolicy(SecurityHeader): 10 | directive = ReferrerPolicyDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, ReferrerPolicyDirective) 14 | 15 | def no_referrer(self): 16 | try: 17 | return ReferrerPolicyDirective.NO_REFERRER in self.parsedstring 18 | except: 19 | return False 20 | 21 | def no_referrer_when_downgrade(self): 22 | try: 23 | return ReferrerPolicyDirective.NO_REFERRER_WHEN_DOWNGRADE in self.parsedstring 24 | except: 25 | return False 26 | 27 | def origin(self): 28 | try: 29 | return ReferrerPolicyDirective.ORIGIN in self.parsedstring 30 | except: 31 | return False 32 | 33 | def origin_when_cross_origin(self): 34 | try: 35 | return ReferrerPolicyDirective.ORIGIN_WHEN_CROSS_ORIGIN in self.parsedstring 36 | except: 37 | return False 38 | 39 | def same_origin(self): 40 | try: 41 | return ReferrerPolicyDirective.SAME_ORIGIN in self.parsedstring 42 | except: 43 | return False 44 | 45 | def strict_origin(self): 46 | try: 47 | return ReferrerPolicyDirective.STRICT_ORIGIN in self.parsedstring 48 | except: 49 | return False 50 | 51 | def strict_origin_when_cross_origin(self): 52 | try: 53 | return ReferrerPolicyDirective.STRICT_ORIGIN_WHEN_CROSS_ORIGIN in self.parsedstring 54 | except: 55 | return False 56 | 57 | def unsafe_url(self): 58 | try: 59 | return ReferrerPolicyDirective.UNSAFE_URL in self.parsedstring 60 | except: 61 | return False 62 | -------------------------------------------------------------------------------- /securityheaders/models/xxssprotection/xxssprotection.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.xxssprotection import XXSSProtectionDirective 3 | from securityheaders.models.annotations import * 4 | 5 | @requiredheader 6 | @description('This header sets the configuration for the cross-site scripting filter built into most browsers. The recommended value is "X-XSS-Protection: 1; mode=block') 7 | @headername('x-xss-protection') 8 | @headerref('https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/dd565647(v=vs.85)') 9 | class XXSSProtection(SecurityHeader): 10 | directive = XXSSProtectionDirective 11 | 12 | def __init__(self, unparsedstring): 13 | SecurityHeader.__init__(self, unparsedstring, XXSSProtectionDirective) 14 | 15 | def one(self): 16 | try: 17 | return XXSSProtectionDirective.ONE in self.parsedstring 18 | except error: 19 | return False 20 | 21 | def zero(self): 22 | try: 23 | return XXSSProtectionDirective.ZERO in self.parsedstring 24 | except error: 25 | return False 26 | 27 | def mode(self): 28 | result = None 29 | if self.parsedstring and XXSSProtectionDirective.MODE in self.parsedstring: 30 | if isinstance(self.parsedstring[XXSSProtectionDirective.MODE], list): 31 | if len(self.parsedstring[XXSSProtectionDirective.MODE]) > 0: 32 | result = self.parsedstring[XXSSProtectionDirective.MODE][0] 33 | else: 34 | result = "" 35 | else: 36 | result = self.parsedstring[XXSSProtectionDirective.MODE] 37 | return result 38 | 39 | def report(self): 40 | result = None 41 | if self.parsedstring and XXSSProtectionDirective.REPORT in self.parsedstring: 42 | if isinstance(self.parsedstring[XXSSProtectionDirective.REPORT], list): 43 | if len(self.parsedstring[XXSSProtectionDirective.REPORT]) > 0: 44 | result = self.parsedstring[XXSSProtectionDirective.REPORT][0] 45 | else: 46 | result = "" 47 | else: 48 | result = self.parsedstring[XXSSProtectionDirective.REPORT] 49 | return result 50 | -------------------------------------------------------------------------------- /securityheaders/models/featurepolicy/featurepolicy.py: -------------------------------------------------------------------------------- 1 | from securityheaders.models import SecurityHeader 2 | from securityheaders.models.featurepolicy import FeaturePolicyDirective, FeaturePolicyKeyword 3 | from securityheaders.models.annotations import * 4 | import copy 5 | 6 | @requiredheader 7 | @description('It is a new header that allows a site to control which features and APIs can be used in the browser.') 8 | @headername('feature-policy') 9 | @headerref('https://wicg.github.io/feature-policy/') 10 | class FeaturePolicy(SecurityHeader): 11 | directive = FeaturePolicyDirective 12 | 13 | def __init__(self, unparsedstring): 14 | SecurityHeader.__init__(self, unparsedstring, FeaturePolicyDirective, FeaturePolicyKeyword) 15 | 16 | def geturls(self, directives=None): 17 | directiveNames = [] 18 | result = [] 19 | if self.parsedstring: 20 | directiveNames = self.keys() 21 | if directives: 22 | directiveNames = directives 23 | for directive in directiveNames: 24 | directiveValues = self.parsedstring[directive] 25 | for directiveValue in directiveValues: 26 | directiveValue = str(directiveValue) 27 | if not FeaturePolicyKeyword.isKeyword(directiveValue) and not FeaturePolicyKeyword.isValue(directiveValue): 28 | result.append(directiveValue) 29 | return result 30 | 31 | def getEffectiveDirectives(self): 32 | return self.getEffectiveFeaturePolicy().getdirectives() 33 | 34 | 35 | def getEffectiveFeaturePolicy(self): 36 | """ returns the effective policy for a given version; i.e. removes keywords/directives that are not relevant 37 | """ 38 | effectivePolicy = FeaturePolicy(None) 39 | effectivePolicy.parsedstring = copy.deepcopy(self.parsedstring) 40 | 41 | for directive in FeaturePolicyDirective: 42 | if directive not in effectivePolicy.keys(): 43 | effectivePolicy.parsedstring[directive] = [directive.getDefaultValue()] 44 | 45 | return effectivePolicy 46 | 47 | 48 | def getEffectiveValues(self, directive): 49 | try: 50 | if isinstance(directive, str): 51 | directive = FeaturePolicyDirective[directive] 52 | return self.getEffectiveFeaturePolicy()[directive] 53 | except Exception: 54 | return [] 55 | -------------------------------------------------------------------------------- /securityheaders/checkers/missingseparatorchecker.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import SyntaxChecker, FindingType, Finding, FindingSeverity 2 | from securityheaders.models import ModelFactory 3 | 4 | class MissingSeparatorChecker(SyntaxChecker): 5 | def check(self, headers, opt_options=dict()): 6 | findings = [] 7 | headernames = ModelFactory().getheadernames() 8 | for header in headernames: 9 | hdr = ModelFactory().getheader(header) 10 | try: 11 | obj = self.extractheader(headers, hdr) 12 | if obj and obj.parsedstring: 13 | findings.extend(self.mycheck(obj)) 14 | except: 15 | pass 16 | return findings 17 | 18 | def mycheck(self, data): 19 | findings = [] 20 | if not data: 21 | return findings 22 | 23 | if not hasattr(data, 'directive'): 24 | return [] 25 | isDirective = data.directive 26 | if not isDirective: 27 | return [] 28 | 29 | directiveseperator = isDirective.directiveseperator() 30 | 31 | for directive in data.keys(): 32 | for value in data[directive]: 33 | value = str(value) 34 | if isDirective.isDirective(value): 35 | finding = Finding(data.headerkey, FindingType.MISSING_SEMICOLON,'Did you forget the '+ str(directiveseperator) + ' character? "' + str(value) + '" seems to be a directive, not a value',FindingSeverity.SYNTAX, directive, value) 36 | if not finding in findings: 37 | findings.append(finding) 38 | for directive2 in list(isDirective): 39 | if str(directive2) in str(value).lower()and not isDirective.isDirective(value) and not str(directive2) +'.' in str(value).lower() and not "_" + str(directive2) in str(value).lower(): #to avoid things like https://*.sandbox.paypal.com or xss_report 40 | finding = Finding(data.headerkey, FindingType.MISSING_SEMICOLON,'Did you forget the '+ str(directiveseperator) + ' character? "' + str(value) + '" seems to be a directive, not a value',FindingSeverity.SYNTAX, directive, value) 41 | if not finding in findings: 42 | findings.append(finding) 43 | 44 | return findings 45 | -------------------------------------------------------------------------------- /securityheaders/checkers/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import Checker 2 | from .checkerfactory import CheckerFactory 3 | from .syntaxchecker import SyntaxChecker 4 | from .infocollector import InfoCollector 5 | from .finding import Finding 6 | from .findingseverity import FindingSeverity 7 | from .findingtype import FindingType 8 | from .headeremptychecker import HeaderEmptyChecker 9 | from .headerevaluator import HeaderEvaluator 10 | from .headerpresentchecker import HeaderPresentChecker 11 | from .infodirectivecollector import InfoDirectiveCollector 12 | from .infoheadercollector import InfoHeaderCollector 13 | from .missingseparatorchecker import MissingSeparatorChecker 14 | from .headerdeprecatedchecker import HeaderDeprecatedChecker 15 | from .infodisclosure import InfoDisclosureChecker 16 | from .infourlcollector import InfoURLCollector 17 | from .headermissingchecker import HeaderMissingChecker 18 | from .directivemissingchecker import MissingDirectiveChecker 19 | from .directiveemptychecker import EmptyDirectiveChecker 20 | 21 | from .unknowndirectivechecker import UnknownDirectiveChecker 22 | 23 | from .cors.allowcredentials import * 24 | from .cors.alloworigin import * 25 | from .cors.maxage import * 26 | from .cors.exposeheaders import * 27 | from .csp import * 28 | from .featurepolicy import * 29 | from .hsts import * 30 | from .referrerpolicy import * 31 | from .server import * 32 | from .xcontenttypeoptions import * 33 | from .xframeoptions import * 34 | from .xpoweredby import * 35 | from .xxssprotection import * 36 | from .other import * 37 | from .expectct import * 38 | from .xpcdp import * 39 | from .setcookie import * 40 | 41 | 42 | import pkgutil 43 | import inspect 44 | 45 | __all__ = ['Checker','InfoCollector','CheckerFactory','SyntaxChecker','Finding','FindingSeverity','FindingType','HeaderEmptyChecker','HeaderDeprecatedChecker','HeaderEvaluator','HeaderPresentChecker','InfoDirectiveCollector','InfoHeaderCollector','InfoHeaderCollector','MissingSeparatorChecker','UnknownDirectiveChecker','InfoDisclosureChecker','InfoURLCollector','HeaderMissingChecker','MissingDirectiveChecker','EmptyDirectiveChecker'] 46 | 47 | for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): 48 | if "test" not in module_name : 49 | module = loader.find_module(module_name).load_module(module_name) 50 | for name, obj in inspect.getmembers(module): 51 | if hasattr(obj, "__name__") and obj.__name__ not in __all__ and inspect.isclass(obj) and issubclass(obj, Checker): 52 | __all__.append(obj.__name__) 53 | -------------------------------------------------------------------------------- /securityheaders/checkers/csp/cspcheck_missingdirective.py: -------------------------------------------------------------------------------- 1 | from securityheaders.checkers import Finding, FindingType, FindingSeverity 2 | from securityheaders import Util 3 | from .cspcheck import CSPCheck 4 | 5 | class CSPCheckMissingDirective(CSPCheck): 6 | 7 | def __init__(self, csp, function): 8 | self.csp = csp 9 | 10 | def check(self): 11 | csp = self.csp 12 | if not csp: 13 | return [] 14 | 15 | findings = [] 16 | 17 | directivesCausingXss = csp.DIRECTIVES_CAUSING_XSS 18 | if csp.parsedstring and csp.directive.DEFAULT_SRC in csp.parsedstring: 19 | defaultSrcValues = csp[csp.directive.DEFAULT_SRC] 20 | if not csp.directive.OBJECT_SRC in csp.parsedstring and (not csp.keyword.NONE in defaultSrcValues or not str(csp.keyword.NONE) in defaultSrcValues): 21 | findings.append(Finding(csp.headerkey, FindingType.MISSING_DIRECTIVES, 'Can you restrict object-src to \'none\'?',FindingSeverity.HIGH_MAYBE, csp.directive.OBJECT_SRC)) 22 | if csp.directive.BASE_URI in csp.parsedstring: 23 | return findings 24 | else: 25 | directivesCausingXss = [csp.directive.BASE_URI] 26 | else: 27 | findings.append(Finding(csp.headerkey, FindingType.MISSING_DIRECTIVES,"The default-src directive should be set as a fall-back when other restrictions have not been specified. ",FindingSeverity.HIGH,csp.directive.DEFAULT_SRC)) 28 | 29 | for directive in directivesCausingXss: 30 | if not csp.parsedstring or not directive in csp.parsedstring: 31 | description = directive.value + ' directive is missing.' 32 | if directive == csp.directive.OBJECT_SRC: 33 | description = 'Missing object-src allows the injection of plugins which can execute JavaScript. Can you set it to \'none\'?' 34 | elif directive == csp.directive.BASE_URI: 35 | if not csp.policyHasScriptNonces() and not csp.policyHasScriptHashes() and csp.policyHasStrictDynamic(): 36 | continue 37 | description = 'Missing base-uri allows the injection of base tags. They can be used to set the base URL for all relative (script) URLs to an attacker controlled domain. Can you set it to \'none\' or \'self\'?' 38 | findings.append(Finding(csp.headerkey, FindingType.MISSING_DIRECTIVES,description,FindingSeverity.HIGH,directive)) 39 | 40 | return findings 41 | --------------------------------------------------------------------------------