diff options
author | Florian Bruhin <git@the-compiler.org> | 2018-07-09 23:38:47 +0200 |
---|---|---|
committer | Florian Bruhin <git@the-compiler.org> | 2018-07-11 17:07:18 +0200 |
commit | c3361c31b370140f323e481dd455450b1e74c099 (patch) | |
tree | c6c93cd74b78610a2610789c6ce907b7e39c5056 /qutebrowser/browser/qutescheme.py | |
parent | 404c276774e689032b0e2c6381bb308c182778de (diff) | |
download | qutebrowser-c3361c31b370140f323e481dd455450b1e74c099.tar.gz qutebrowser-c3361c31b370140f323e481dd455450b1e74c099.zip |
CVE-2018-10895: Fix CSRF issues with qute://settings/set URLv1.2.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.py | 34 |
1 files changed, 30 insertions, 4 deletions
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 8866f1643..436a96e19 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -31,10 +31,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 @@ -45,6 +53,7 @@ from qutebrowser.misc import objects pyeval_output = ":pyeval was never called" spawn_output = ":spawn was never called" +csrf_token = None _HANDLERS = {} @@ -447,13 +456,30 @@ 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) - html = jinja.render('settings.html', title='settings', - configdata=configdata, - confget=config.instance.get_str) - return 'text/html', html + # 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, + csrf_token=csrf_token) + return 'text/html', src @add_handler('bindings') |