-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
csp-analyzer.py
executable file
·215 lines (179 loc) · 9.19 KB
/
csp-analyzer.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/python3
import sys
import requests
import urllib.parse
from colored import fg, bg, attr
import tldextract
def banner():
print("""
_
___ ___ _ __ __ _ _ __ __ _| |_ _ _______ _ __ _ __ _ _
/ __/ __| '_ \ / _` | '_ \ / _` | | | | |_ / _ \ '__| | '_ \| | | |
| (__\__ \ |_) | | (_| | | | | (_| | | |_| |/ / __/ | _ | |_) | |_| |
\___|___/ .__/ \__,_|_| |_|\__,_|_|\__, /___\___|_| (_) | .__/ \__, |
|_| |___/ |_| |___/
by @gwendallecoguic
""")
pass
banner()
# Sources:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# https://content-security-policy.com/
t_help = {
"child-src": "Defines the valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>.",
"connect-src": "Restricts the URLs which can be loaded using script interfaces",
"default-src": "Serves as a fallback for the other fetch directives.",
"font-src": "Specifies valid sources for fonts loaded using @font-face.",
"frame-src": "Specifies valid sources for nested browsing contexts loading using elements such as <frame> and <iframe>.",
"img-src": "Specifies valid sources of images and favicons.",
"manifest-src": "Specifies valid sources of application manifest files.",
"media-src": "Specifies valid sources for loading media using the <audio> , <video> and <track> elements.",
"object-src": "Specifies valid sources for the <object>, <embed>, and <applet> elements.",
"prefetch-src": "Specifies valid sources to be prefetched or prerendered.",
"script-src": "Specifies valid sources for JavaScript.",
"style-src": "Specifies valid sources for stylesheets.",
"webrtc-src": "Specifies valid sources for WebRTC connections.",
"worker-src": "Specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts.",
"base-uri": "Restricts the URLs which can be used in a document's <base> element.",
"plugin-types": "Restricts the set of plugins that can be embedded into a document by limiting the types of resources which can be loaded.",
"sandbox": "Enables a sandbox for the requested resource similar to the <iframe> sandbox attribute.",
"disown-opener": "Ensures a resource will disown its opener when navigated to.",
"form-action": "Restricts the URLs which can be used as the target of a form submissions from a given context.",
"frame-ancestors": "Specifies valid parents that may embed a page using <frame>, <iframe>, <object>, <embed>, or <applet>.",
"navigate-to": "Restricts the URLs to which a document can navigate by any means (a, form, window.location, window.open, etc.)",
"report-uri": "Instructs the user agent to report attempts to violate the Content Security Policy. These violation reports consist of JSON documents sent via an HTTP POST request to the specified URI.",
"report-to": "Fires a SecurityPolicyViolationEvent.",
"block-all-mixed-content": "Prevents loading any assets using HTTP when the page is loaded using HTTPS.",
"referrer": "Used to specify information in the referer (sic) header for links away from a page. Use the Referrer-Policy header instead.",
"require-sri-for": "Requires the use of SRI for scripts or styles on the page.",
"upgrade-insecure-requests": "Instructs user agents to treat all of a site's insecure URLs (those served over HTTP) as though they have been replaced with secure URLs (those served over HTTPS). This directive is intended for web sites with large numbers of insecure legacy URLs that need to be rewritten.",
"*": {"t":"Wildcard, allows any URL except data: blob: filesystem: schemes.","c":"red"},
"'none'": {"t":"Prevents loading resources from any source.","c":"green"},
"'self'": {"t":"Allows loading resources from the same origin (same scheme, host and port).","c":"green"},
"data:": {"t":"Allows loading resources via the data scheme (eg Base64 encoded images).","c":"yellow"},
"blob:": {"t":"Allows loading resources via the blob scheme (eg Base64 encoded images).","c":"yellow"},
"domain.example.com": {"t":"Allows loading resources from the specified domain name.","c":"green"},
"*.example.com": {"t":"Allows loading resources from any subdomain under example.com.","c":"green"},
"https://cdn.com": {"t":"Allows loading resources only over HTTPS matching the given domain.","c":"green"},
"https:": {"t":"Allows loading resources only over HTTPS on any domain.","c":"green"},
"'unsafe-inline'": {"t":"Allows use of inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to) and javascript: URIs.","c":"red"},
"'unsafe-eval'": {"t":"Allows unsafe dynamic code evaluation such as JavaScript eval()","c":"red"},
"'nonce-'": {"t":"Allows script or style tag to execute if the nonce attribute value matches the header value. Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.","c":"green"},
"'sha256-'": {"t":"Allow a specific script or style to execute if it matches the hash. Doesn't work for javascript: URIs. Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.","c":"green"},
}
t_warning_level = {
0: 'white',
1: 'cyan',
2: 'green',
3: 'yellow',
4: 'dark_orange',
5: 'red',
}
def usage( err='' ):
print( "Usage: %s <url> [<cookies>]" % sys.argv[0] )
if err:
print( "Error: %s!" % err )
sys.exit()
if len(sys.argv) < 2:
usage( 'url not found' )
if len(sys.argv) > 3:
usage()
url = sys.argv[1]
if len(sys.argv) > 2:
# cookies = sys.argv[2]
t_cookies = {}
for c in sys.argv[2].split(';'):
c = c.strip()
if len(c):
i = c.index('=')
k = c[0:i]
v = c[i+1:]
# print(c.index('='))
# print(k)
# print(v)
t_cookies[k] = v
else:
t_cookies = {}
# print(t_cookies)
if not url.startswith('http'):
url = 'https://' + url
# exit()
print("Calling %s..." % url )
# r = requests.get( url )
r = requests.get(url, cookies=t_cookies, allow_redirects=False, headers={'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0'})
# print(r.headers)
# print(r.text)
if 'Content-Security-Policy' not in r.headers:
usage( 'Content-Security-Policy not found' )
#print("%s" % r.headers['Content-Security-Policy'] )
t_csp = r.headers['Content-Security-Policy'].split( ';' )
#print(" %s" % t_csp )
print("")
t_parse_orig = urllib.parse.urlparse( url )
t_tld_orig = tldextract.extract( t_parse_orig.netloc )
# print( t_parse_orig )
def getWarningLevel( t_tld_orig, item ):
w_level = 0
if item in t_help:
return 0
if not item.startswith('http'):
item = 'https://'+item
tmp_parse = urllib.parse.urlparse( item )
tmp_tld = tldextract.extract( tmp_parse.netloc )
# print(tmp_parse)
if tmp_tld.subdomain == t_tld_orig.subdomain and tmp_tld.domain == t_tld_orig.domain and tmp_tld.suffix == t_tld_orig.suffix:
# same subdomain and domain and tld
w_level = 1
elif tmp_tld.domain == t_tld_orig.domain and tmp_tld.suffix == t_tld_orig.suffix:
# same domain and tld
w_level = 2
elif tmp_tld.domain == t_tld_orig.domain:
# same domain name
w_level = 3
else:
# nothing in common
w_level = 4
if '*' in tmp_parse.netloc:
# it's a wildcard!
w_level+=1
return w_level
for csp in t_csp:
csp = csp.strip()
if not len(csp):
continue
tmp = csp.split( ' ' )
policy = tmp.pop( 0 )
if policy:
if not len(policy):
continue
#sys.stdout.write( " " )
sys.stdout.write("%s%s%s%s" % (fg('cyan'),attr('reverse'),policy,attr(0)) )
# sys.stdout.write( colored( "%s" % policy, 'cyan', attrs=['reverse'] ) )
if policy in t_help:
sys.stdout.write(" %s[%s]%s" % (fg('light_gray'),t_help[policy],attr(0)))
# sys.stdout.write( colored( " [%s]" % t_help[policy], 'white' ) )
sys.stdout.write( "\n" )
for item in tmp:
if not len(item):
continue
orig_item = item
if item.startswith("'nonce-"):
item = "'nonce-'"
elif item.startswith("'sha256-"):
item = "'sha256-'"
if item in t_help:
color = t_help[item]['c']
else:
w_level = getWarningLevel( t_tld_orig, item )
color = t_warning_level[w_level]
if color == 'white':
sys.stdout.write( " + " )
else:
sys.stdout.write(" %s + %s" % (fg(color),attr(0)) )
# sys.stdout.write( colored( " + ", color ) )
sys.stdout.write( "%s" % orig_item )
if item in t_help:
sys.stdout.write( " %s[%s]%s" % (fg(color),t_help[item]['t'],attr(0)) )
# sys.stdout.write( colored( " [%s]" % t_help[item]['t'], color ) )
sys.stdout.write( "\n" )
sys.stdout.write( "\n" )