def check(self, headers, opt_options=dict()): findings = [] expectct = self.getexpectct(headers) if not expectct: return findings findings = [] if not expectct.enforce(): 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)) if expectct.maxage() == 0: 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')) elif expectct.maxage() and expectct.maxage() < 3000: 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()))) return findings
def checkIP(self, directive, directiveValues, findings): csp = self.csp for value in directiveValues: url = '//' + Util.getSchemeFreeUrl(value) host = urlparse(url).netloc ip = None validip = True try: ip = ipaddress.ip_address(u'' + host) except ValueError: validip = False if validip: ipString = str(ip) + '' if '127.0.0.1' in ipString: 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)) else: 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))
def mycheck(self, data): findings = [] if not data: return findings directiveclazz = data.directive if not hasattr(directiveclazz, 'anydirective') or ( hasattr(directiveclazz, 'anydirective') and not directiveclazz.anydirective): seperator = directiveclazz.directivevalueseperator() for directive in data.keys(): if not directiveclazz.isDirective(directive): if directive.endswith(seperator): findings.append( Finding( data.headerkey, FindingType.UNKNOWN_DIRECTIVE, str(data.headerkey) + " directives don't end with a " + str(seperator), FindingSeverity.SYNTAX, None, directive)) else: findings.append( Finding( data.headerkey, FindingType.UNKNOWN_DIRECTIVE, 'Directive "' + str(directive) + '" is not a known ' + str(data.headerkey) + ' directive.', FindingSeverity.SYNTAX, directive)) return findings
def mycheck(self, data): findings = [] if not data: return findings if not hasattr(data, 'directive'): return [] isDirective = data.directive if not isDirective: return [] directiveseperator = isDirective.directiveseperator() for directive in data.keys(): for value in data[directive]: value = str(value) if isDirective.isDirective(value): 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) if not finding in findings: findings.append(finding) for directive2 in list(isDirective): 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 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) if not finding in findings: findings.append(finding) return findings
def check(self, opt_options=dict()): csp = self.csp if not csp: return [] findings = [] angular = [] jsonp = [] jsonpeval = [] if 'bypasses' not in opt_options.keys(): bypasses = [] else: bypasses = opt_options['bypasses'] directive = csp.getEffectiveDirective(csp.directive.OBJECT_SRC) objectSrcValues = [] try: objectSrcValues = csp[directive] except KeyError: objectSrcValues = [] if csp.directive.PLUGIN_TYPES in csp.parsedstring: pluginTypes = csp[csp.directive.PLUGIN_TYPES] else: pluginTypes = None if pluginTypes and not 'application/x-shockwave-flash' in pluginTypes: return [] for value in objectSrcValues: if value == csp.keyword.NONE: return [] url = '//' + Util.getSchemeFreeUrl(value) flashBypass = Util.matchWildcardUrls(url, bypasses) if (flashBypass): 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)) elif (directive == csp.directive.OBJECT_SRC): findings.append( Finding(csp.headerkey, FindingType.OBJECT_WHITELIST_BYPASS, 'Can you restrict object-src to \'none\' only?', FindingSeverity.MEDIUM_MAYBE, directive, value)) return findings
def checkframing(self, directive, directiveValues, findings): csp = self.csp 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'." for value in directiveValues: if directive == csp.directive.FRAME_ANCESTORS: if self.__notcontains_keyword__(value, csp.keyword.NONE) and self.__notcontains_keyword__(value, csp.keyword.SELF): findings.append(Finding(csp.headerkey, FindingType.ALLOW_FROM,description,FindingSeverity.MEDIUM_MAYBE, directive, value))
def check(self, headers, opt_options=dict()): policy = self.getfeaturepolicy(headers) if not policy or not policy.parsedstring: return [] findings = [] directivesToCheck = policy.getEffectiveDirectives() for directive in directivesToCheck: values = policy.getEffectiveValues(directive) for value in values: url = Util.getSchemeFreeUrl(value) if url and str(FeaturePolicyKeyword.STAR) in url and len( url ) == 1 and directive != FeaturePolicyDirective.PICTURE_IN_PICTURE: 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)) return findings
def mycheck(self, headers, header, description, options): if not header or not headers: return [] if header in headers.keys(): return [Finding(header, FindingType.INFO_DISCLOSURE, header + ' header present. ' + description,FindingSeverity.INFO, None, headers[header])] return []
def check(self): csp = self.csp if not csp or not csp.parsedstring: return [] findings = [] directivesToCheck = csp.getEffectiveDirectives( csp.DIRECTIVES_CAUSING_XSS) for directive in directivesToCheck: values = [] if directive in csp.parsedstring: values = csp[directive] for value in values: url = Util.getSchemeFreeUrl(value) if '*' in url and len(url) == 1: 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)) return findings
def check(self, headers, opt_options=dict()): opts = self.getxframeoptions(headers) csp = self.getcsp(headers) if not opts or not csp: return [] value = None if csp.directive.FRAME_ANCESTORS in csp.keys(): value = csp[csp.directive.FRAME_ANCESTORS] if not value: return [] inconsistent = False if opts.deny() and not self.__notcontains_keyword__( value, csp.keyword.NONE): inconsistent = True elif opts.sameorigin() and not self.__notcontains_keyword__( value, csp.keyword.SELF): inconsistent = True elif opts.allowfrom() not in value: inconsistent = True if inconsistent: 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) ] return []
def check(self, headers, opt_options=dict()): cookie = self.getcookie(headers) if not cookie: return [] if not cookie.secure() and requires_security(cookie): return [Finding(SetCookie.headerkey, FindingType.INSECURE_HEADER, 'The cookie does not have the secure flag set.', FindingSeverity.MEDIUM,SetCookie.directive.SECURE, None)] return []
def check(self, headers, opt_options=dict()): opts = self.getxframeoptions(headers) if not opts: return [] if opts.allowfrom(): directive = opts.directives()[0] if opts.directives() else None 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)] return []
def check(self): csp = self.csp if not csp: return [] findings = [] directivesCausingXss = csp.DIRECTIVES_CAUSING_XSS if csp.parsedstring and csp.directive.DEFAULT_SRC in csp.parsedstring: defaultSrcValues = csp[csp.directive.DEFAULT_SRC] if not csp.directive.OBJECT_SRC in csp.parsedstring and ( not csp.keyword.NONE in defaultSrcValues or not str(csp.keyword.NONE) in defaultSrcValues): findings.append( Finding(csp.headerkey, FindingType.MISSING_DIRECTIVES, 'Can you restrict object-src to \'none\'?', FindingSeverity.HIGH_MAYBE, csp.directive.OBJECT_SRC)) if csp.directive.BASE_URI in csp.parsedstring: return findings else: directivesCausingXss = [csp.directive.BASE_URI] else: 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)) for directive in directivesCausingXss: if not csp.parsedstring or not directive in csp.parsedstring: description = directive.value + ' directive is missing.' if directive == csp.directive.OBJECT_SRC: description = 'Missing object-src allows the injection of plugins which can execute JavaScript. Can you set it to \'none\'?' elif directive == csp.directive.BASE_URI: if not csp.policyHasScriptNonces( ) and not csp.policyHasScriptHashes( ) and csp.policyHasStrictDynamic(): continue 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\'?' findings.append( Finding(csp.headerkey, FindingType.MISSING_DIRECTIVES, description, FindingSeverity.HIGH, directive)) return findings
def check(self, headers, opt_options=dict()): if not headers: return [] findings = [] for header in headers.keys(): findings.append( Finding(header, FindingType.INFO_HEADER, headers[header], FindingSeverity.NONE, None, None)) return findings
def check(self, headers, opt_options=dict()): findings = [] hsts = self.gethsts(headers) if not hsts: return findings if hsts.maxAge() == 0: 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)] return findings
def checkNonce(self, directive, directiveValues, findings): nonce_pattern = re.compile("^'nonce-(.+)'$") for value in directiveValues: value = str(value) match = nonce_pattern.search(value) #nonce value starts with nonce- but does not have an actual value if not bool(match) and "nonce-" in value: findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should be at least 8 characters long.',FindingSeverity.MEDIUM, directive, value)) #no nonce value in the directive value if not bool(match): continue #nonce value in the directive value else: #get the value of the nonce; i.e. everything after nonce- nonceValue = match.group(1) if len(nonceValue) < 8: findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should be at least 8 characters long.',FindingSeverity.MEDIUM, directive, value)) if not self.csp.isNonce(value, True): findings.append(Finding(self.csp.headerkey,FindingType.NONCE_LENGTH,'Nonces should only use the base64 charset.',FindingSeverity.INFO, directive, value))
def mycheck(self, data): if not data: return [] if not data.hasdirectives() and isinstance(data, SecurityHeader): return [ Finding(data.headerkey, FindingType.MISSING_HEADER, str(data.headerkey) + ' header is empty.', FindingSeverity.INFO, data.headerkey) ] return []
def check(self, headers, opt_options=dict()): rocsp = CSPReportOnlyChecker.getcsp(self, headers) csp = CSPChecker.getcsp(self, headers) if not csp and rocsp: description = "The CSP is not enforced as only the content-security-policy-report-only header is present. Can you set the content-security-policy?" return [ Finding(rocsp.headerkey, FindingType.REPORT_ONLY, description, FindingSeverity.INFO, None, None) ] return []
def check(self, headers, opt_options=dict()): xxss = self.getxxss(headers) if xxss and not xxss.one(): 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) ] return []
def check(self, headers, opt_options=dict()): origins = self.getorigins(headers) if origins and origins.isstar(): return [ Finding( AccessControlAllowOrigin.headerkey, FindingType.STAR_ORIGIN, str(AccessControlAllowOrigin.headerkey) + " should not be *", FindingSeverity.HIGH, AccessControlAllowOriginDirective.STAR, None) ] return []
def check(self, headers, opt_options=dict()): xxss = self.getxxss(headers) if xxss and xxss.report() and xxss.report().startswith('http:'): 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()) ] return []
def checksrchttp(self, directive, directiveValues, findings): csp = self.csp for value in directiveValues: description = None if directive == csp.directive.REPORT_URI: description = 'Use HTTPS to send violation reports securely.' else: description = 'Allow only resources downloaded over HTTPS.' if value.startswith('http://'): findings.append( Finding(csp.headerkey, FindingType.SRC_HTTP, description, FindingSeverity.MEDIUM, directive, value))
def check(self, headers, opt_options=dict()): findings = [] policy = self.getreferrerpolicy(headers) if not policy: return findings if policy.unsafe_url(): 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)) if policy.origin_when_cross_origin(): 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)) return findings
def check(self, headers, opt_options=dict()): maxage = self.getmaxage(headers) if maxage and maxage.maxage() and maxage.maxage() > 1800: 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())) ] return []
def check(self, headers, opt_options=dict()): opts = self.getxcontenttypeoptions(headers) if not opts: return [] if not opts.nosniff(): directive = opts.directives()[0] if opts.directives() else None 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) ] return []
def mycheck(self, headers, header): if not header or not headers: return [] if header in headers.keys() or header.lower() in headers.keys(): value = headers[header] if header in headers.keys() else headers[ header.lower()] return [ Finding( header, FindingType.DEPRECATED_HEADER, header + ' header present. This header is deprecated and should not be used.', FindingSeverity.INFO, None, value) ] return []
def check(self): csp = self.csp if not csp or not csp.parsedstring: return [] findings = [] if csp.directive.REPORT_URI in csp.parsedstring: 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)) return findings return []
def check(self, headers, opt_options=dict()): findings = [] hsts = self.gethsts(headers) if not hsts: return findings if not hsts.includesubdomains(): return [ Finding(HSTS.headerkey, FindingType.NO_SUBDOMAINS, 'include subdomains was not specified.', FindingSeverity.LOW, HSTS.directive.INCLUDESUBDOMAINS) ] return findings
def check(self, headers, opt_options=dict()): findings = [] expectct = self.getexpectct(headers) if not expectct: return findings findings = [] if expectct.reporturi() and expectct.reporturi().startswith('http://'): findings.append( Finding( expectct.headerkey, FindingType.SRC_HTTP, expectct.headerkey + 'communicates its reports via an insecure channel.', FindingSeverity.LOW, expectct.reporturi())) return findings
def check(self, headers, opt_options=dict()): headers = self.getexposeheaders(headers) if not headers: return [] result = [] for header in headers.headers(): if self.__issensitive__(header): 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)) return result