summaryrefslogtreecommitdiff
path: root/qutebrowser/browser/qutescheme.py
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2018-07-09 23:38:47 +0200
committerFlorian Bruhin <git@the-compiler.org>2018-07-11 17:07:59 +0200
commitc2ff32d92ba9bf40ff53498ee04a4124d4993c85 (patch)
treeaa8130a54e7ea9b58f2b4192954deff26fd97d12 /qutebrowser/browser/qutescheme.py
parent718f73be2eb062f4893a463bca34c18feeded251 (diff)
downloadqutebrowser-v1.3.x.tar.gz
qutebrowser-v1.3.x.zip
CVE-2018-10895: Fix CSRF issues with qute://settings/set URLv1.3.x
In ffc29ee043ae7336d9b9dcc029a05bf7a3f994e8 (part of v1.0.0), a qute://settings/set URL was added to change settings. Contrary to what I apparently believed at the time, it *is* possible for websites to access `qute://*` URLs (i.e., neither QtWebKit nor QtWebEngine prohibit such requests, other than the usual cross-origin rules). In other words, this means a website can e.g. have an `<img>` tag which loads a `qute://settings/set` URL, which then sets `editor.command` to a bash script. The result of that is arbitrary code execution. Fixes #4060 See #2332 (cherry picked from commit 43e58ac865ff862c2008c510fc5f7627e10b4660)
Diffstat (limited to 'qutebrowser/browser/qutescheme.py')
-rw-r--r--qutebrowser/browser/qutescheme.py28
1 files changed, 27 insertions, 1 deletions
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index e3777c389..bd5448ca1 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -32,10 +32,18 @@ import textwrap
import mimetypes
import urllib
import collections
+import base64
+
+try:
+ import secrets
+except ImportError:
+ # New in Python 3.6
+ secrets = None
import pkg_resources
import sip
from PyQt5.QtCore import QUrlQuery, QUrl
+from PyQt5.QtNetwork import QNetworkReply
import qutebrowser
from qutebrowser.config import config, configdata, configexc, configdiff
@@ -46,6 +54,7 @@ from qutebrowser.misc import objects
pyeval_output = ":pyeval was never called"
spawn_output = ":spawn was never called"
+csrf_token = None
_HANDLERS = {}
@@ -449,12 +458,29 @@ def _qute_settings_set(url):
@add_handler('settings')
def qute_settings(url):
"""Handler for qute://settings. View/change qute configuration."""
+ global csrf_token
+
if url.path() == '/set':
+ if url.password() != csrf_token:
+ message.error("Invalid CSRF token for qute://settings!")
+ raise QuteSchemeError("Invalid CSRF token!",
+ QNetworkReply.ContentAccessDenied)
return _qute_settings_set(url)
+ # Requests to qute://settings/set should only be allowed from
+ # qute://settings. As an additional security precaution, we generate a CSRF
+ # token to use here.
+ if secrets:
+ csrf_token = secrets.token_urlsafe()
+ else:
+ # On Python < 3.6, from secrets.py
+ token = base64.urlsafe_b64encode(os.urandom(32))
+ csrf_token = token.rstrip(b'=').decode('ascii')
+
src = jinja.render('settings.html', title='settings',
configdata=configdata,
- confget=config.instance.get_str)
+ confget=config.instance.get_str,
+ csrf_token=csrf_token)
return 'text/html', src