-
Notifications
You must be signed in to change notification settings - Fork 38
/
csp_parser.py
172 lines (143 loc) · 5.33 KB
/
csp_parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
@author: moloch
A basic Content Security Policy parser.
"""
# pylint: disable=C0103,C0111,R0201
from collections import defaultdict
from urlparse import urlparse
### Constants
BASE_URI = "base-uri"
FORM_ACTION = "form-action"
FRAME_ANCESTORS = "frame-ancestors"
PLUGIN_TYPES = "plugin-types"
REPORT_URI = "report-uri"
SANDBOX = "sandbox"
UPGRADE_INSECURE_REQUESTS = "upgrade-insecure-requests"
REFLECTIVE_XSS = "reflected-xss"
REFERRER = "referrer"
DEFAULT_SRC = "default-src"
SCRIPT_SRC = "script-src"
CHILD_SRC = "child-src"
FRAME_SRC = "frame-src"
CONNECT_SRC = "connect-src"
FONT_SRC = "font-src"
IMG_SRC = "img-src"
MEDIA_SRC = "media-src"
OBJECT_SRC = "object-src"
STYLE_SRC = "style-src"
MANIFEST_SRC = "manifest-src"
SELF = "'self'"
NONE = "'none'"
UNSAFE_INLINE = "'unsafe-inline'"
UNSAFE_EVAL = "'unsafe-eval'"
HTTP = "http:"
HTTPS = "https:"
BLOB = "blob:"
DATA = "data:"
FILESYSTEM = "filesystem:"
MEDIASTREAM = "mediastream:"
def csp_match_domains(content_src, domain):
""" Does a `content_src' allow a `domain' """
# Isolate just the domain incase there is a scheme/etc.
content_src = content_src.lower()
domain = domain.lower()
if urlparse(content_src).netloc != '':
content_src = urlparse(content_src).netloc
src_parts = content_src.split(".")[::-1] # Reverse the domains
domain_parts = domain.split(".")[::-1]
for index, src_part in enumerate(src_parts):
if src_part == "*":
return True
if src_part == domain_parts[index]:
continue
else:
return False
return len(src_parts) == len(domain_parts)
class ContentSecurityPolicy(object):
"""
A simple Content-Security-Policy object, it resembles a dictionary but has
logic to return `default-src' when approiate, etc.
"""
HEADERS = ["content-security-policy",
"content-security-policy-report-only",
"x-content-security-policy",
"x-webkit-csp"]
# All content directives
CONTENT_DIRECTIVES = [
DEFAULT_SRC, SCRIPT_SRC, CHILD_SRC, FRAME_SRC, CONNECT_SRC, FONT_SRC,
IMG_SRC, MEDIA_SRC, OBJECT_SRC, STYLE_SRC, MANIFEST_SRC,
BASE_URI, FORM_ACTION, FRAME_ANCESTORS, PLUGIN_TYPES,
REPORT_URI, SANDBOX, REFLECTIVE_XSS, REFERRER]
# These directives do not fallback to default-src
NO_FALLBACK = [BASE_URI, FORM_ACTION, FRAME_ANCESTORS, PLUGIN_TYPES,
REPORT_URI, SANDBOX, REFLECTIVE_XSS, REFERRER]
def __init__(self, header_name, header_value):
self._content_policies = defaultdict(list)
self._header_name = None
self._header_value = None
self.header_name = header_name
self.header_value = header_value
@property
def header_name(self):
return self._header_name
@header_name.setter
def header_name(self, value):
""" Setter for the header name """
if value.lower() not in self.HEADERS:
raise ValueError("Unknown header name: '%s'" % value)
else:
self._header_name = value.lower()
@property
def header_value(self):
return self._header_value
@header_value.setter
def header_value(self, value):
""" Sets the header value and parses it """
self._header_value = value.lower()
self._parse_header()
def _parse_header(self):
""" Splits the header on ';' then subsequently on whitespace """
for policy in self._header_value.split(";"):
if not len(policy):
continue # Skip blanks
directive, sources = self._unpack_policy(*policy.strip().split(" "))
self[directive] = sources
def _unpack_policy(self, directive, *content_sources):
""" Used to unpack the directive name and directives """
return directive, [src.strip() for src in content_sources]
def is_deprecated_header(self):
""" Check for X-WebKit-CSP or X-Content-Security-Policy """
return self.header_name.startswith('x')
def is_report_only_mode(self):
return self.header_name.endswith("report-only")
def iteritems(self):
""" Similar to a dictionary, iterates tuples of key/value pairs """
for key in self.CONTENT_DIRECTIVES:
yield (key, self[key],)
def __setitem__(self, key, value):
if key not in self.CONTENT_DIRECTIVES:
raise ValueError("Unknown directive '%s'" % key)
if isinstance(value, list):
self._content_policies[key].extend(value)
elif isinstance(value, basestring):
self._content_policies[key].append(value)
else:
raise ValueError("Expected list or basestring")
def __getitem__(self, key):
"""
Get the policy or return default-src if the policy isn't in NO_FALLBACK
"""
if key not in self.CONTENT_DIRECTIVES:
raise ValueError("Unknown directive '%s'" % key)
if key in self._content_policies:
return self._content_policies[key]
elif key not in self.NO_FALLBACK:
return self._content_policies[DEFAULT_SRC]
def __contains__(self, item):
if item not in self.NO_FALLBACK and item in self.CONTENT_DIRECTIVES:
return True
else:
return item in self._content_policies
def __iter__(self):
for key in self.CONTENT_DIRECTIVES:
yield key