Skip to content

Commit 14e8130

Browse files
committed
Rewrite cross-site and cross-host requests detection
Make sure browsers send referrers so we can track cross-site requests (could be used to identify which sites user hosts) This breaks /raw because there are no referrers there fixes #227 fixes #223 fixes #224
1 parent 1ba9f2b commit 14e8130

2 files changed

Lines changed: 76 additions & 23 deletions

File tree

src/Ui/UiRequest.py

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class SecurityError(Exception):
4949

5050

5151
@PluginManager.acceptPlugins
52-
class UiRequest(object):
52+
class UiRequest:
5353

5454
def __init__(self, server, get, env, start_response):
5555
if server:
@@ -99,8 +99,52 @@ def isDomain(self, address):
9999
def resolveDomain(self, domain):
100100
return self.server.site_manager.resolveDomainCached(domain)
101101

102-
# Call the request handler function base on path
102+
def isCrossOriginRequest(self):
103+
"""Prevent detecting sites on this 0net instance
104+
105+
In particular, we block non-user requests from other hosts as well as
106+
cross-site
107+
"""
108+
109+
url = self.getRequestUrl()
110+
fetch_mode = self.env.get('HTTP_SEC_FETCH_MODE')
111+
origin = self.env.get('HTTP_ORIGIN')
112+
referer = self.env.get('HTTP_REFERER')
113+
114+
# Allow all user-initiated requests
115+
if fetch_mode == 'navigate':
116+
return False
117+
118+
# Deny requests that cannot be traced
119+
if not origin and not referer:
120+
return True
121+
122+
# Deny requests from non-0net origins
123+
if origin and not self.isSameHost(origin, url):
124+
return True
125+
126+
# Allow non-site specific requests
127+
if self.getRequestSite() == '/':
128+
return False
129+
130+
# Deny cross site requests
131+
if not self.isSameOrigin(referer, url):
132+
return True
133+
134+
return False
135+
103136
def route(self, path):
137+
"""Main routing
138+
139+
If no internal action is performed, calls action[Path] from plugins
140+
141+
This behaviour is not very flexible or easy to follow, so perhaps
142+
we'd want something else..
143+
"""
144+
145+
if self.isCrossOriginRequest():
146+
return self.error404()
147+
104148
# Restict Ui access by ip
105149
if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:
106150
return self.error403(details=False)
@@ -284,37 +328,43 @@ def isScriptNonceSupported(self):
284328
is_script_nonce_supported = True
285329
return is_script_nonce_supported
286330

331+
def getRequestSite(self):
332+
"""Return 0net site addr associated with current request
333+
334+
If request is site-agnostic, returns /
335+
"""
336+
path = self.env["PATH_INFO"]
337+
match = re.match(r'(/raw)?(?P<site>/1[a-zA-Z0-9]*)', path)
338+
if not match:
339+
match = re.match(r'(/raw)?/(?P<domain>[a-zA-Z0-9\.\-_]*)', path)
340+
if match:
341+
domain = match.group('domain')
342+
if self.isDomain(domain):
343+
addr = self.resolveDomain(domain)
344+
return '/'+addr
345+
return '/'
346+
return match.group('site')
347+
287348
# Send response headers
288349
def sendHeader(self, status=200, content_type="text/html", noscript=False, allow_ajax=False, script_nonce=None, extra_headers=[]):
289-
url = self.getRequestUrl()
290-
referer = self.env.get('HTTP_REFERER')
291-
origin = self.env.get('HTTP_ORIGIN')
292-
fetch_site = self.env.get('HTTP_SEC_FETCH_SITE')
293-
fetch_mode = self.env.get('HTTP_SEC_FETCH_MODE')
294-
not_same_ref = referer and not self.isSameHost(referer, url)
295-
not_same_origin = origin and not self.isSameHost(origin, url)
296-
cross_site_not_navigate = not referer and fetch_site == 'cross-site' and not fetch_mode == 'navigate'
297-
if status != 404 and (not_same_ref or not_same_origin or cross_site_not_navigate):
298-
# pretend nothing is here for third-party access
299-
return self.error404()
300-
301350
headers = {}
302351
headers["Version"] = "HTTP/1.1"
303352
headers["Connection"] = "Keep-Alive"
304353
headers["Keep-Alive"] = "max=25, timeout=30"
305354
headers["X-Frame-Options"] = "SAMEORIGIN"
355+
headers["Referrer-Policy"] = "same-origin"
306356

307357
if noscript:
308358
headers["Content-Security-Policy"] = "default-src 'none'; sandbox allow-top-navigation allow-forms; img-src *; font-src * data:; media-src *; style-src * 'unsafe-inline';"
309359
elif script_nonce and self.isScriptNonceSupported():
310-
headers["Content-Security-Policy"] = "default-src 'none'; script-src 'nonce-{0}'; img-src 'self' blob: data:; style-src 'self' blob: 'unsafe-inline'; connect-src *; frame-src 'self' blob:".format(script_nonce)
360+
headers["Content-Security-Policy"] = f"default-src 'none'; script-src 'nonce-{script_nonce}'; img-src 'self' blob: data:; style-src 'self' blob: 'unsafe-inline'; connect-src *; frame-src 'self' blob:"
311361

312362
if allow_ajax:
313363
headers["Access-Control-Allow-Origin"] = "null"
314364

315365
if self.env["REQUEST_METHOD"] == "OPTIONS":
316366
# Allow json access
317-
headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Cookie, Range"
367+
headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Cookie, Range, Referer"
318368
headers["Access-Control-Allow-Credentials"] = "true"
319369

320370
# Download instead of display file types that can be dangerous
@@ -624,15 +674,18 @@ def isSameHost(self, url_a, url_b):
624674
if not url_a or not url_b:
625675
return False
626676

627-
url_a = url_a.replace("/raw/", "/")
628-
url_b = url_b.replace("/raw/", "/")
677+
host_pattern = r'(?P<host>http[s]?://.*?)(/|$)'
629678

630-
origin_pattern = "http[s]{0,1}://(.*?/).*"
679+
match_a = re.match(host_pattern, url_a)
680+
match_b = re.match(host_pattern, url_b)
631681

632-
origin_a = re.sub(origin_pattern, "\\1", url_a)
633-
origin_b = re.sub(origin_pattern, "\\1", url_b)
682+
if not match_a or not match_b:
683+
return False
634684

635-
return origin_a == origin_b
685+
host_a = match_a.group('host')
686+
host_b = match_b.group('host')
687+
688+
return host_a == host_b
636689

637690
def isSameOrigin(self, url_a, url_b):
638691
"""Check if 0net origin is the same"""

src/Ui/template/wrapper.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ <h3>ZeroNet requires JavaScript support.</h3>If you use NoScript/Tor browser: Cl
7474

7575

7676
<!-- Site Iframe -->
77-
<iframe src='about:blank' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups allow-modals allow-presentation allow-pointer-lock allow-popups-to-escape-sandbox {sandbox_permissions}" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" oallowfullscreen="true" msallowfullscreen="true"></iframe>
77+
<iframe src='about:blank' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups allow-modals allow-presentation allow-pointer-lock allow-popups-to-escape-sandbox allow-same-origin {sandbox_permissions}" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" oallowfullscreen="true" msallowfullscreen="true" referrerpolicy="same-origin"></iframe>
7878

7979
<!-- Site info -->
8080
<script id="script_init" nonce="{script_nonce}">

0 commit comments

Comments
 (0)