From 6c5298884240c3978159db4a5db9bf2c87ed6c1e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 20 May 2017 12:34:00 +1000 Subject: [PATCH 1/3] Harden some response headers --- onionshare/web.py | 53 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 2ff60bd2..0d9e4133 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -21,7 +21,7 @@ from distutils.version import StrictVersion as Version import queue, mimetypes, platform, os, sys, socket, logging from urllib.request import urlopen -from flask import Flask, Response, request, render_template_string, abort +from flask import Flask, Response, request, render_template_string, abort, make_response from flask import __version__ as flask_version from . import strings, common @@ -175,16 +175,31 @@ def index(slug_candidate): global stay_open, download_in_progress deny_download = not stay_open and download_in_progress if deny_download: - return render_template_string(open(common.get_resource_path('html/denied.html')).read()) + r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'self\' data:;') + r.headers.set('X-Frame-Options', 'DENY') + r.headers.set('X-Xss-Protection', '1; mode=block') + r.headers.set('X-Content-Type-Options', 'nosniff') + r.headers.set('Referrer-Policy', 'no-referrer') + r.headers.set('Server', 'Onion') + return r # If download is allowed to continue, serve download page - return render_template_string( + + r = make_response(render_template_string( open(common.get_resource_path('html/index.html')).read(), slug=slug, file_info=file_info, filename=os.path.basename(zip_filename), filesize=zip_filesize, - filesize_human=common.human_readable_filesize(zip_filesize)) + filesize_human=common.human_readable_filesize(zip_filesize))) + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') + r.headers.set('X-Frame-Options', 'DENY') + r.headers.set('X-Xss-Protection', '1; mode=block') + r.headers.set('X-Content-Type-Options', 'nosniff') + r.headers.set('Referrer-Policy', 'no-referrer') + r.headers.set('Server', 'Onion') + return r # If the client closes the OnionShare window while a download is in progress, # it should immediately stop serving the file. The client_cancel global is @@ -203,7 +218,14 @@ def download(slug_candidate): global stay_open, download_in_progress deny_download = not stay_open and download_in_progress if deny_download: - return render_template_string(open(common.get_resource_path('html/denied.html')).read()) + r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') + r.headers.set('X-Frame-Options', 'DENY') + r.headers.set('X-Xss-Protection', '1; mode=block') + r.headers.set('X-Content-Type-Options', 'nosniff') + r.headers.set('Referrer-Policy', 'no-referrer') + r.headers.set('Server', 'Onion') + return r global download_count @@ -286,13 +308,19 @@ def download(slug_candidate): shutdown_func() r = Response(generate()) - r.headers.add('Content-Length', zip_filesize) - r.headers.add('Content-Disposition', 'attachment', filename=basename) + r.headers.set('Content-Length', zip_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') + r.headers.set('X-Frame-Options', 'DENY') + r.headers.set('X-Xss-Protection', '1; mode=block') + r.headers.set('X-Content-Type-Options', 'nosniff') + r.headers.set('Referrer-Policy', 'no-referrer') + r.headers.set('Server', 'Onion') # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: - r.headers.add('Content-Type', content_type) + r.headers.set('Content-Type', content_type) return r @@ -311,7 +339,14 @@ def page_not_found(e): force_shutdown() print(strings._('error_rate_limit')) - return render_template_string(open(common.get_resource_path('html/404.html')).read()) + r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read())) + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') + r.headers.set('X-Frame-Options', 'DENY') + r.headers.set('X-Xss-Protection', '1; mode=block') + r.headers.set('X-Content-Type-Options', 'nosniff') + r.headers.set('Referrer-Policy', 'no-referrer') + r.headers.set('Server', 'Onion') + return r # shutting down the server only works within the context of flask, so the easiest way to do it is over http shutdown_slug = common.random_string(16) From 38ee7fde21e6c0216137376b390dec6a46408670 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 21 May 2017 10:03:18 +1000 Subject: [PATCH 2/3] Remove duplication of security headers --- onionshare/web.py | 51 +++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 0d9e4133..36901f39 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -47,6 +47,16 @@ file_info = [] zip_filename = None zip_filesize = None + +security_headers = [ + ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'self\' data:;'), + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'Onion') +] + def set_file_info(filenames, processed_size_callback=None): """ Using the list of filenames being shared, fill in details that the web @@ -176,12 +186,8 @@ def index(slug_candidate): deny_download = not stay_open and download_in_progress if deny_download: r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'self\' data:;') - r.headers.set('X-Frame-Options', 'DENY') - r.headers.set('X-Xss-Protection', '1; mode=block') - r.headers.set('X-Content-Type-Options', 'nosniff') - r.headers.set('Referrer-Policy', 'no-referrer') - r.headers.set('Server', 'Onion') + for header,value in security_headers: + r.headers.set(header, value) return r # If download is allowed to continue, serve download page @@ -193,12 +199,8 @@ def index(slug_candidate): filename=os.path.basename(zip_filename), filesize=zip_filesize, filesize_human=common.human_readable_filesize(zip_filesize))) - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') - r.headers.set('X-Frame-Options', 'DENY') - r.headers.set('X-Xss-Protection', '1; mode=block') - r.headers.set('X-Content-Type-Options', 'nosniff') - r.headers.set('Referrer-Policy', 'no-referrer') - r.headers.set('Server', 'Onion') + for header,value in security_headers: + r.headers.set(header, value) return r # If the client closes the OnionShare window while a download is in progress, @@ -219,12 +221,8 @@ def download(slug_candidate): deny_download = not stay_open and download_in_progress if deny_download: r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') - r.headers.set('X-Frame-Options', 'DENY') - r.headers.set('X-Xss-Protection', '1; mode=block') - r.headers.set('X-Content-Type-Options', 'nosniff') - r.headers.set('Referrer-Policy', 'no-referrer') - r.headers.set('Server', 'Onion') + for header,value in security_headers: + r.headers.set(header, value) return r global download_count @@ -310,13 +308,8 @@ def download(slug_candidate): r = Response(generate()) r.headers.set('Content-Length', zip_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') - r.headers.set('X-Frame-Options', 'DENY') - r.headers.set('X-Xss-Protection', '1; mode=block') - r.headers.set('X-Content-Type-Options', 'nosniff') - r.headers.set('Referrer-Policy', 'no-referrer') - r.headers.set('Server', 'Onion') - + for header,value in security_headers: + r.headers.set(header, value) # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: @@ -340,12 +333,8 @@ def page_not_found(e): print(strings._('error_rate_limit')) r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read())) - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'unsafe-inline\' data:;') - r.headers.set('X-Frame-Options', 'DENY') - r.headers.set('X-Xss-Protection', '1; mode=block') - r.headers.set('X-Content-Type-Options', 'nosniff') - r.headers.set('Referrer-Policy', 'no-referrer') - r.headers.set('Server', 'Onion') + for header,value in security_headers: + r.headers.set(header, value) return r # shutting down the server only works within the context of flask, so the easiest way to do it is over http From 16f4d5f3ca70fdbd4e2cf4dc8c5e2371ac9c9255 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 21 May 2017 10:30:37 +1000 Subject: [PATCH 3/3] use the actual OnionShare version as the Server header string --- onionshare/web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 36901f39..68b7fcb2 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -47,14 +47,14 @@ file_info = [] zip_filename = None zip_filesize = None - +strings.load_strings(common) security_headers = [ ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; img-src \'self\' data:;'), ('X-Frame-Options', 'DENY'), ('X-Xss-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), ('Referrer-Policy', 'no-referrer'), - ('Server', 'Onion') + ('Server', strings._('version_string').format(common.get_version())) ] def set_file_info(filenames, processed_size_callback=None):