From d35b5a9acdd6d6c0a3f39084192a824eae56a1f7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Apr 2016 16:49:46 -0700 Subject: [PATCH] Added rate-limiting to GUI. After 20 404 errors, server automatically stops (#238) --- onionshare/web.py | 16 ++++++++++++++++ onionshare_gui/onionshare_gui.py | 10 +++++++++- resources/locale/en.json | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index ebff67b2..bba98fd2 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -74,6 +74,7 @@ REQUEST_DOWNLOAD = 1 REQUEST_PROGRESS = 2 REQUEST_OTHER = 3 REQUEST_CANCELED = 4 +REQUEST_RATE_LIMIT = 4 q = queue.Queue() @@ -95,6 +96,7 @@ def generate_slug(): slug = helpers.build_slug() download_count = 0 +error404_count = 0 stay_open = False def set_stay_open(new_stay_open): @@ -290,6 +292,20 @@ def page_not_found(e): 404 error page. """ add_request(REQUEST_OTHER, request.path) + + global error404_count + if request.path != '/favicon.ico': + error404_count += 1 + if error404_count == 20: + add_request(REQUEST_RATE_LIMIT, request.path) + + # Learn the port the Flask app is running on, to stop it + # http://stackoverflow.com/questions/5085656/how-to-get-the-current-port-number-in-flask + port = int(request.host.split(':')[1]) + stop(port) + + print(strings._('error_rate_limit')) + return render_template_string(open(helpers.get_resource_path('html/404.html')).read()) # shutting down the server only works within the context of flask, so the easiest way to do it is over http diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3e559313..1eb508bc 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -142,6 +142,10 @@ class OnionShareGui(QtWidgets.QMainWindow): Start the onionshare server. This uses multiple threads to start the Tor hidden server and the web app. """ + # Reset web counters + web.download_count = 0 + web.error404_count = 0 + # start the hidden service self.status_bar.showMessage(strings._('gui_starting_server1', True)) self.app.choose_port() @@ -214,6 +218,10 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == web.REQUEST_DOWNLOAD: self.downloads.add_download(event["data"]["id"], web.zip_filesize) + elif event["type"] == web.REQUEST_RATE_LIMIT: + self.stop_server() + alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + elif event["type"] == web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) @@ -227,7 +235,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.downloads.cancel_download(event["data"]["id"]) elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded', True), event["path"])) + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) def copy_url(self): """ diff --git a/resources/locale/en.json b/resources/locale/en.json index b4ab3e88..88a0cbb3 100644 --- a/resources/locale/en.json +++ b/resources/locale/en.json @@ -48,5 +48,6 @@ "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_quit_warning": "Are you sure you want to quit?\nThe URL you are sharing won't exist anymore.", "gui_quit_warning_quit": "Quit", - "gui_quit_warning_dont_quit": "Don't Quit" + "gui_quit_warning_dont_quit": "Don't Quit", + "error_rate_limit": "An attacker might be trying to guess your URL. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new URL." }