def testPolicy_isBasicPolicy(self): assert Policy.INVALID().isBasicPolicy() == False assert Policy([PolicyTest.sampleDirective6]).isBasicPolicy() == False assert Policy([PolicyTest.sampleDirective1a]).isBasicPolicy() == True assert Policy( [PolicyTest.sampleDirective2, PolicyTest.sampleDirective4]).isBasicPolicy() == False
def testPolicy_asBasicPolicies_multiple(self): assert Policy( [PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2]).asBasicPolicies() == set([ Policy([PolicyTest.sampleDirective1a]), Policy([PolicyTest.sampleDirective2]) ])
def testPolicy_matches_defaultSrcNotSpecified_noMatch(self): """Policy contains no directive of resource type, and no default directive either. Should assume 'default-src *' (no match for inline/eval resources).""" pol = Policy((PolicyTest.sampleDirective5, )) selfURI = PolicyTest.sampleURI1a assert not pol.matches(URI.INLINE(), "script-src", selfURI) assert not pol.matches(URI.EVAL(), "script-src", selfURI)
def testPolicy_matches_defaultSrcNotSpecified_noMatch(self): """Policy contains no directive of resource type, and no default directive either. Should assume 'default-src *' (no match for inline/eval resources).""" pol = Policy((PolicyTest.sampleDirective5,)) selfURI = PolicyTest.sampleURI1a assert not pol.matches(URI.INLINE(), "script-src", selfURI) assert not pol.matches(URI.EVAL(), "script-src", selfURI)
def testPolicy_matches_nonMatchingDirectiveTypeButDefaultMatches(self): """Policy contains directive of resource type that does not match and default directive that does match, but it should not be applied.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert not pol.matches(PolicyTest.sampleURI1a, "connect-src", selfURI)
def testPolicy_init_duplicateType(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective3, PolicyTest.sampleDirective4)) directives = pol.getDirectives() assert PolicyTest.sampleDirective1a in directives and (PolicyTest.sampleDirective3 in directives \ or PolicyTest.sampleDirective4 in directives)
def testPolicy_init_noDuplicatesHere(self): directives = set([ PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective5 ]) pol = Policy(directives) assert pol.getDirectives() == directives
def testPolicy_withoutPaths(self): withPaths = Policy([PolicyTest.sampleDirective3, PolicyTest.sampleDirective5, PolicyTest.sampleDirective7]) withoutPaths = Policy([PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(), PolicyTest.sampleDirective7]) assert withPaths.withoutPaths() == withoutPaths assert withoutPaths.withoutPaths() == withoutPaths assert Policy.INVALID().withoutPaths() == Policy.INVALID()
def testPolicy_hasDefaultDirective(self): assert Policy.INVALID().hasDefaultDirective() == False assert Policy( [PolicyTest.sampleDirective2, PolicyTest.sampleDirective9]).hasDefaultDirective() == True assert Policy( [PolicyTest.sampleDirective5, PolicyTest.sampleDirective6]).hasDefaultDirective() == False
def testPolicy_init_duplicateDirective(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2)) assert pol == Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2)) assert pol == Policy( (PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2))
def testLogEntry_generatePolicy_incomplete(self): logEntryNoReport = LogEntryTest.logEntryData.copy() del logEntryNoReport['csp-report'] logEntryNoPolicyType = LogEntryTest.logEntryData.copy() del logEntryNoPolicyType['policy-type'] assert LogEntry(logEntryNoReport).generatePolicy() == Policy.INVALID() assert LogEntry( logEntryNoPolicyType).generatePolicy() == Policy.INVALID()
def testPolicy_init_removeNotRegularDirective(self): pol = Policy([ PolicyTest.sampleDirective1a, Directive.INVALID(), Directive.EVAL_SCRIPT_BASE_RESTRICTION() ]) expected = Policy([PolicyTest.sampleDirective1a]) assert pol == expected
def testPolicy_matches_matchingDirectiveType(self): """Policy contains directive of resource type that matches.""" pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches( URI("https", "abc.seclab.nu", 443, "/path", "some-query"), "connect-src", selfURI)
def testPolicy_withoutPaths_schemeOnly(self): withPaths = Policy( [PolicyTest.sampleDirective3, PolicyTest.sampleDirective5]) withoutPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(["http"]) ]) assert withPaths.withoutPaths(["http"]) == withoutPaths
def testPolicy_combinedPolicy_validDefaultSrcOnly(self): """Combination of two policies with default directive is possible only if both policies contain only a default directive.""" pol1 = Policy([PolicyTest.sampleDirective1a]) pol2 = Policy([PolicyTest.sampleDirective9]) expected = Policy([Directive("default-src", [PolicyTest.sampleSourceExpression1, SourceExpression.UNSAFE_INLINE()])]) assert pol1.combinedPolicy(pol2) == expected assert pol2.combinedPolicy(pol1) == expected
def testPolicy_compareTo_recursive(self): pol1 = Policy([PolicyTest.sampleDirective6]) pol2 = Policy( [Directive("style-src", [SourceExpression.UNSAFE_INLINE()])]) assert pol1.compareTo(pol2) == ( set([pol2]), set([ Policy([Directive("style-src", [SelfSourceExpression.SELF()])]) ]), set([]))
def testPolicy_matches_invalid(self): """An invalid policy matches nothing.""" selfURI = PolicyTest.sampleURI2 assert not Policy.INVALID().matches(PolicyTest.sampleURI1a, "script-src", selfURI) assert not Policy.INVALID().matches(URI.INVALID(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.EMPTY(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.INLINE(), "script-src", selfURI) assert not Policy.INVALID().matches(URI.EVAL(), "script-src", selfURI)
def testReport_generatePolicy_missingReportField(self): reportNoViolated = Report({ "blocked-uri": ReportTest.sampleURI1a, "document-uri": ReportTest.sampleURI2 }) reportNoBlocked = Report({ "violated-directive": ReportTest.sampleDirective2a, "document-uri": ReportTest.sampleURI2 }) assert reportNoViolated.generatePolicy("regular") == Policy.INVALID() assert reportNoBlocked.generatePolicy("regular") == Policy.INVALID()
def testPolicy_eq(self): pol1a = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) pol1b = Policy( (PolicyTest.sampleDirective1b, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) pol2 = Policy( (PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) assert Policy.INVALID() == Policy.INVALID() assert pol1a == pol1b assert hash(pol1a) == hash(pol1b) assert pol1a != pol2
def testPolicy_compareTo_regular(self): pol1 = Policy([PolicyTest.sampleDirective7, PolicyTest.sampleDirective8]) pol2 = Policy([PolicyTest.sampleDirective7, PolicyTest.sampleDirective9]) assert pol1.compareTo(pol2) == (set([Policy([PolicyTest.sampleDirective7])]), set([Policy([PolicyTest.sampleDirective8])]), set([Policy([PolicyTest.sampleDirective9])])) assert pol2.compareTo(pol1) == (set([Policy([PolicyTest.sampleDirective7])]), set([Policy([PolicyTest.sampleDirective9])]), set([Policy([PolicyTest.sampleDirective8])])) assert pol1.compareTo(pol1) == (set([Policy([PolicyTest.sampleDirective7]), Policy([PolicyTest.sampleDirective8])]), set([]), set([]))
def testReport_generatePolicy_fromInvalidDirectiveResult(self): reportDefaultSrc = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective1a, "document-uri": ReportTest.sampleURI2 }) assert reportDefaultSrc.generatePolicy("regular") == Policy.INVALID()
class PolicyDataReaderTest(unittest.TestCase): samplePolicy = Policy([ Directive("default-src", ()), Directive("style-src", [SourceExpression.UNSAFE_INLINE()]), Directive("img-src", [URISourceExpression(None, "seclab.nu", "*", None)]) ]) @pytest.fixture(autouse=True) def initdir(self, tmpdir): tmpdir.chdir() def setUp(self): self.fileIn = PolicyDataReader(True) self.filename = "policystorage.dat" self.fileOut = DataWriter(self.filename) def testReportCreation(self): """Writes a LogEntry and loads it back as an object.""" self.fileOut.storeAll([PolicyDataReaderTest.samplePolicy]) self.fileOut.close() dataOut = self.fileIn.loadAll(self.filename) assert len(dataOut) == 1 assert PolicyDataReaderTest.samplePolicy in dataOut
def testPolicy_str_normal(self): pol = Policy( (PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3)) assert str( pol ) == "default-src http://seclab.nu; img-src *; script-src 'unsafe-inline'"
def testPolicyParser_parse_ignoredDirective(self): """Ensure that unsupported directives ('report-uri' etc.) are skipped without causing an error.""" policy = """img-src *; report-uri /csp.cgi""" cspPolicy = PolicyParser(strict=True, ignoredTypes=("report-uri", )).parse(policy) assert cspPolicy == Policy([PolicyTest.sampleDirective3])
def generatePolicy(self): """ Generates a new policy that allows exactly the kind of event that caused the CSP violation report in this log entry (but nothing more). This assumes that (1) the violation report contains a specific violated-directive field (it may not be 'default-src'), and (2) that the log entry contains the type of the violation report ('regular', 'eval' or 'inline'). If any inconsistent log entries/reports are used to generate policies, the policies themselves will be inconsistent. The result of this method is a Policy. It is Policy.INVALID() if (1) the violated directive is missing, Directive.INVALID() or 'default-src', (2) if this log entry has none of the types 'regular', 'inline' or 'eval', (3) the 'blocked-uri' is URI.INVALID() or URI.EMPTY() in the 'regular' case. The result is a basic Policy containing one whitelisted resource (corresponding to the violated directive). In practice, it should not be used alone, but be combined with basic policies generated for other violations on the same web site. It should also be prepended with "default-src 'none'" to ensure that only the whitelisted resources are allowed. (The standard behaviour of CSP in absence of any default Directive is to assume "default-src *", which may not be the desired behaviour.) In particular, policies should be collected with CSP headers like these: Content-Security-Policy-Report-Only: default-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; object-src 'none'; style-src 'unsafe-inline'; img-src 'none'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'; report-uri /csp.cgi?type=regular Content-Security-Policy-Report-Only: default-src *; script-src * 'unsafe-inline'; style-src * 'unsafe-inline'; report-uri /csp.cgi?type=eval Content-Security-Policy-Report-Only: default-src *; script-src * 'unsafe-eval'; style-src *; report-uri /csp.cgi?type=inline The results should also be filtered to ensure that only reports sent by fully compatible browsers are taken into account. """ if (self == LogEntry.INVALID() or 'policy-type' not in self or 'csp-report' not in self): return Policy.INVALID() return self['csp-report'].generatePolicy(self['policy-type'])
def testReport_generatePolicy_regular(self): report = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective2a, "document-uri": ReportTest.sampleURI2 }) assert report.generatePolicy("regular") == Policy( [ReportTest.sampleDirective3])
def testReport_generatePolicy_wrongDocumentURI(self): reportEmptyDocument = Report({ "blocked-uri": ReportTest.sampleURI1a, "violated-directive": ReportTest.sampleDirective1a, "document-uri": URI.EMPTY() }) assert reportEmptyDocument.generatePolicy( "regular") == Policy.INVALID()
def testPolicy_combinedPolicy_invalidPolicy(self): pol = Policy([ PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3 ]) assert pol.combinedPolicy(Policy.INVALID()) == Policy.INVALID() assert Policy.INVALID().combinedPolicy(pol) == Policy.INVALID()
def testPolicy_compareTo_invalid(self): assert Policy.INVALID().compareTo(Policy.INVALID()) == (set([]), set([]), set([])) pol = Policy([PolicyTest.sampleDirective9]) assert pol.compareTo(Policy.INVALID()) == (set([]), set([]), set([])) assert Policy.INVALID().compareTo(pol) == (set([]), set([]), set([]))
class ReportDataReaderTest(unittest.TestCase): sampleURI1a = URI("http", "seclab.nu", None, None, None) sampleURI1b = URI("http", "seclab.nu", None, None, None) sampleURI2 = URI("http", "seclab.nu", None, "/blocked", "query") sampleDirective1a = Directive("default-src", ()) sampleDirective1b = Directive("default-src", ()) sampleDirective2a = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) sampleDirective2b = Directive("script-src", (SourceExpression.UNSAFE_INLINE(), )) samplePolicy1a = Policy((sampleDirective1a, sampleDirective2a)) samplePolicy1b = Policy((sampleDirective1b, sampleDirective2b)) samplePolicy2 = Policy((sampleDirective1a, )) @pytest.fixture(autouse=True) def initdir(self, tmpdir): tmpdir.chdir() def setUp(self): self.fileIn = ReportDataReader(True) self.filename = "encodingdecoding.dat" self.fileOut = DataWriter(self.filename) def tearDown(self): pass def testReportCreation(self): """Writes a Report and loads it back as an object.""" report = Report({ "abc": True, "def": 1, "ghi": "http://seclab.nu/", "document-uri": ReportDataReaderTest.sampleURI1a, "violated-directive": ReportDataReaderTest.sampleDirective1a, "original-policy": ReportDataReaderTest.samplePolicy1a, "blocked-uri": ReportDataReaderTest.sampleURI2 }) self.fileOut.storeAll([report]) self.fileOut.close() dataOut = self.fileIn.loadAll(self.filename) assert len(dataOut) == 1 print report print dataOut[0] assert report in dataOut
def testPolicy_withoutPaths(self): withPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5, PolicyTest.sampleDirective7 ]) withoutPaths = Policy([ PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(), PolicyTest.sampleDirective7 ]) assert withPaths.withoutPaths() == withoutPaths assert withoutPaths.withoutPaths() == withoutPaths assert Policy.INVALID().withoutPaths() == Policy.INVALID()
def testPolicyParser_parse_noDefaultSrcRewriting(self): policy = """default-src 'self' http://seclab.nu""" cspPolicy = PolicyParser(expandDefaultSrc=False, defaultSrcTypes=("img-src", "connect-src")).parse(policy) assert cspPolicy == Policy([ Directive("default-src", [ PolicyTest.sampleSourceExpression1, PolicyTest.sampleSourceExpression2 ]) ])
def testPolicy_combinedPolicy_normal(self): pol1 = Policy([ PolicyTest.sampleDirective6, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3 ]) pol2 = Policy( [PolicyTest.sampleDirective4, PolicyTest.sampleDirective7]) expected = Policy([ PolicyTest.sampleDirective6, PolicyTest.sampleDirective3, Directive("script-src", [ SourceExpression.UNSAFE_EVAL(), SourceExpression.UNSAFE_INLINE() ]) ]) assert pol1.combinedPolicy(pol1) == pol1 assert pol2.combinedPolicy(pol2) == pol2 assert pol1.combinedPolicy(pol2) == expected assert pol2.combinedPolicy(pol1) == expected
def testPolicyParser_parse_duplicates(self): """The CSP standard mandates that only the first directive of each type should be used.""" duplicatePolicy = """connect-src 'self' chrome-extension: https://abc.seclab.nu/path; """ \ + """font-src 'self' http://seclab.nu; """ \ + """connect-src 'self' https://example.com""" cspPolicy = PolicyParser().parse(duplicatePolicy) assert cspPolicy == Policy([ PolicyTest.sampleDirective5, Directive("font-src", [ PolicyTest.sampleSourceExpression1, PolicyTest.sampleSourceExpression2 ]) ])
def testPolicy_combinedPolicy_normal(self): pol1 = Policy([PolicyTest.sampleDirective6, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3]) pol2 = Policy([PolicyTest.sampleDirective4, PolicyTest.sampleDirective7]) expected = Policy([PolicyTest.sampleDirective6, PolicyTest.sampleDirective3, Directive("script-src", [SourceExpression.UNSAFE_EVAL(), SourceExpression.UNSAFE_INLINE()])]) assert pol1.combinedPolicy(pol1) == pol1 assert pol2.combinedPolicy(pol2) == pol2 assert pol1.combinedPolicy(pol2) == expected assert pol2.combinedPolicy(pol1) == expected
def testPolicy_matches_defaultSrcMatches(self): """Policy contains no directive of resource type, but a default directive that matches.""" pol = Policy((PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches(PolicyTest.sampleURI1a, "script-src", selfURI)
def testPolicy_combinedPolicy_invalidPolicy(self): pol = Policy([PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective3]) assert pol.combinedPolicy(Policy.INVALID()) == Policy.INVALID() assert Policy.INVALID().combinedPolicy(pol) == Policy.INVALID()
def testPolicy_compareTo_recursive(self): pol1 = Policy([PolicyTest.sampleDirective6]) pol2 = Policy([Directive("style-src", [SourceExpression.UNSAFE_INLINE()])]) assert pol1.compareTo(pol2) == (set([pol2]), set([Policy([Directive("style-src", [SelfSourceExpression.SELF()])])]), set([]))
def testPolicy_combinedPolicy_invalidDefaultSrcAndOtherDirective(self): pol1 = Policy([PolicyTest.sampleDirective1a, PolicyTest.sampleDirective3]) pol2 = Policy([PolicyTest.sampleDirective9]) assert pol1.combinedPolicy(pol2) == Policy.INVALID() assert pol2.combinedPolicy(pol1) == Policy.INVALID()
def testPolicy_combinedPolicy_invalidDefaultSrcInOnePolicyOnly(self): pol1 = Policy([PolicyTest.sampleDirective3]) pol2 = Policy([PolicyTest.sampleDirective9]) assert pol1.combinedPolicy(pol2) == Policy.INVALID() assert pol2.combinedPolicy(pol1) == Policy.INVALID()
def testPolicy_init_duplicateType(self): pol = Policy((PolicyTest.sampleDirective1a, PolicyTest.sampleDirective3, PolicyTest.sampleDirective4)) directives = pol.getDirectives() assert PolicyTest.sampleDirective1a in directives and (PolicyTest.sampleDirective3 in directives \ or PolicyTest.sampleDirective4 in directives)
def testPolicy_matches_defaultSrcNoMatch(self): """Policy contains no directive of resource type, but a default directive. Default-src does not match.""" pol = Policy((PolicyTest.sampleDirective1a,)) selfURI = PolicyTest.sampleURI1a assert not pol.matches(PolicyTest.sampleURI2, "img-src", selfURI)
def testPolicy_matches_defaultSrcNotSpecified_match(self): """Policy contains no directive of resource type, and no default directive either. Should assume 'default-src *' (match for regular resources).""" pol = Policy((PolicyTest.sampleDirective5,)) selfURI = PolicyTest.sampleURI1a assert pol.matches(PolicyTest.sampleURI2, "script-src", selfURI)
def testPolicy_matches_nonMatchingDirectiveTypeButDefaultMatches(self): """Policy contains directive of resource type that does not match and default directive that does match, but it should not be applied.""" pol = Policy((PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert not pol.matches(PolicyTest.sampleURI1a, "connect-src", selfURI)
def testPolicy_matches_matchingDirectiveType(self): """Policy contains directive of resource type that matches.""" pol = Policy((PolicyTest.sampleDirective1a, PolicyTest.sampleDirective5)) selfURI = PolicyTest.sampleURI2 assert pol.matches(URI("https", "abc.seclab.nu", 443, "/path", "some-query"), "connect-src", selfURI)
def testPolicy_matches_defaultSrcNotUsable(self): """Policy contains no directive of resource type, but a default directive. Default-src cannot be used in this case because not allowed for resource type.""" pol = Policy((PolicyTest.sampleDirective1a,)) selfURI = PolicyTest.sampleURI2 assert not pol.matches(PolicyTest.sampleURI1a, "form-action", selfURI)
def testPolicy_withoutPaths_schemeOnly(self): withPaths = Policy([PolicyTest.sampleDirective3, PolicyTest.sampleDirective5]) withoutPaths = Policy([PolicyTest.sampleDirective3, PolicyTest.sampleDirective5.withoutPaths(["http"])]) assert withPaths.withoutPaths(["http"]) == withoutPaths
def testPolicy_init_noDuplicatesHere(self): directives = set([PolicyTest.sampleDirective1a, PolicyTest.sampleDirective2, PolicyTest.sampleDirective5]) pol = Policy(directives) assert pol.getDirectives() == directives