From 4a408843b0ef816daf70a472a02b78cd6073a4d5 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 16 Jan 2016 17:48:22 +0100 Subject: Protect download urls against CSRF using unique request tokens (#1490642) Send X-Frame-Options headers with every HTTP response --- plugins/enigma/enigma.js | 2 +- plugins/enigma/lib/enigma_ui.php | 2 ++ .../lib/Roundcube/rcube_sieve_engine.php | 2 ++ plugins/managesieve/managesieve.js | 2 +- plugins/zipdownload/zipdownload.js | 2 +- plugins/zipdownload/zipdownload.php | 6 +++++- program/include/rcmail.php | 2 ++ program/include/rcmail_output_html.php | 6 +++--- program/js/app.js | 24 ++++++++++++++-------- program/lib/Roundcube/rcube_message.php | 3 ++- program/lib/Roundcube/rcube_output.php | 5 +++++ program/steps/addressbook/export.inc | 2 ++ program/steps/mail/get.inc | 4 ++++ program/steps/mail/viewsource.inc | 4 ++++ 14 files changed, 50 insertions(+), 16 deletions(-) diff --git a/plugins/enigma/enigma.js b/plugins/enigma/enigma.js index bd52d047a..a5497f4b3 100644 --- a/plugins/enigma/enigma.js +++ b/plugins/enigma/enigma.js @@ -157,7 +157,7 @@ rcube_webmail.prototype.enigma_export = function(selected) if (!keys.length) return; - this.goto_url('plugin.enigmakeys', {_a: 'export', _keys: keys}); + this.goto_url('plugin.enigmakeys', {_a: 'export', _keys: keys}, false, true); }; // Submit key(s) import form diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php index dfdacc7a6..ca28c321a 100644 --- a/plugins/enigma/lib/enigma_ui.php +++ b/plugins/enigma/lib/enigma_ui.php @@ -459,6 +459,8 @@ class enigma_ui */ private function key_export() { + $this->rc->request_security_check(rcube_utils::INPUT_GET); + $keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_GPC); $engine = $this->enigma->load_engine(); $list = $keys == '*' ? $engine->list_keys() : explode(',', $keys); diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php index 67c921161..3fb168443 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php @@ -397,6 +397,8 @@ class rcube_sieve_engine } } else if ($action == 'setget') { + $this->rc->request_security_check(rcube_utils::INPUT_GET); + $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); $script = $this->sieve->get_script($script_name); diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js index a69fa5a58..117f01a5a 100644 --- a/plugins/managesieve/managesieve.js +++ b/plugins/managesieve/managesieve.js @@ -181,7 +181,7 @@ rcube_webmail.prototype.managesieve_setget = function() var id = this.filtersets_list.get_single_selection(), script = this.env.filtersets[id]; - location.href = this.env.comm_path+'&_action=plugin.managesieve-action&_act=setget&_set='+urlencode(script); + this.goto_url('plugin.managesieve-action', {_act: 'setget', _set: script}, false, true); }; // Set activate/deactivate request diff --git a/plugins/zipdownload/zipdownload.js b/plugins/zipdownload/zipdownload.js index 228b04f8f..6f918d298 100644 --- a/plugins/zipdownload/zipdownload.js +++ b/plugins/zipdownload/zipdownload.js @@ -54,7 +54,7 @@ function rcmail_zipdownload(mode) // default .eml download of single message if (mode == 'eml') { var uid = rcmail.get_single_uid(); - rcmail.goto_url('viewsource', rcmail.params_from_uid(uid, {_save: 1})); + rcmail.goto_url('viewsource', rcmail.params_from_uid(uid, {_save: 1}), false, true); return; } diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index 2928f4978..241de5489 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -63,7 +63,7 @@ class zipdownload extends rcube_plugin '_action' => 'plugin.zipdownload.attachments', '_mbox' => $rcmail->output->env['mailbox'], '_uid' => $rcmail->output->env['uid'], - )); + ), false, false, true); $link = html::a(array('href' => $href, 'class' => 'button zipdownload'), rcube::Q($this->gettext('downloadall')) @@ -120,6 +120,10 @@ class zipdownload extends rcube_plugin public function download_attachments() { $rcmail = rcmail::get_instance(); + + // require CSRF protected request + $rcmail->request_security_check(rcube_utils::INPUT_GET); + $imap = $rcmail->get_storage(); $temp_dir = $rcmail->config->get('temp_dir'); $tmpfname = tempnam($temp_dir, 'zipdownload'); diff --git a/program/include/rcmail.php b/program/include/rcmail.php index bdf9405db..0294392a6 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -840,6 +840,8 @@ class rcmail extends rcube // this need to be full url to make redirects work $absolute = true; } + else if ($secure && ($token = $this->get_request_token())) + $url .= $delm . '_token=' . urlencode($token); if ($absolute || $full) { // add base path to this Roundcube installation diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index f6020107d..8dda8c3c1 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -516,10 +516,10 @@ EOF; // write all javascript commands $this->add_script($commands, 'head_top'); - // send clickjacking protection headers + // allow (legal) iframe content to be loaded $iframe = $this->framed || $this->env['framed']; - if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin'))) { - header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe)); + if (!headers_sent() && $iframe && $this->app->config->get('x_frame_options', 'sameorigin') === 'deny') { + header('X-Frame-Options: sameorigin', true); } // call super method diff --git a/program/js/app.js b/program/js/app.js index 36b6d1d4c..075600c3d 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1024,7 +1024,7 @@ function rcube_webmail() break; } - this.goto_url('get', qstring+'&_download=1', false); + this.goto_url('get', qstring+'&_download=1', false, true); break; case 'select-all': @@ -1230,10 +1230,10 @@ function rcube_webmail() case 'download': if (this.env.action == 'get') { - location.href = location.href.replace(/_frame=/, '_download='); + location.href = this.secure_url(location.href.replace(/_frame=/, '_download=')); } else if (uid = this.get_single_uid()) { - this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1})); + this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1}), false, true); } break; @@ -1321,13 +1321,13 @@ function rcube_webmail() case 'export': if (this.contact_list.rowcount > 0) { - this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request }); + this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request }, false, true); } break; case 'export-selected': if (this.contact_list.rowcount > 0) { - this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') }); + this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') }, false, true); } break; @@ -1442,7 +1442,7 @@ function rcube_webmail() if (task == 'mail') url += '&_mbox=INBOX'; else if (task == 'logout' && !this.env.server_error) { - url += '&_token=' + this.env.request_token; + url = this.secure_url(url); this.clear_compose_data(); } @@ -1491,6 +1491,12 @@ function rcube_webmail() return url + '?' + name + '=' + value; }; + // append CSRF protection token to the given url + this.secure_url = function(url) + { + return this.add_url(url, '_token', this.env.request_token); + }, + this.is_framed = function() { return this.env.framed && parent.rcmail && parent.rcmail != this && typeof parent.rcmail.command == 'function'; @@ -7899,9 +7905,11 @@ function rcube_webmail() } }; - this.goto_url = function(action, query, lock) + this.goto_url = function(action, query, lock, secure) { - this.redirect(this.url(action, query), lock); + var url = this.url(action, query) + if (secure) url = this.secure_url(url); + this.redirect(url, lock); }; this.location_href = function(url, target, frame) diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index d21de34e6..10ebbf69f 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -108,7 +108,8 @@ class rcube_message 'get_url' => $this->app->url(array( 'action' => 'get', 'mbox' => $this->folder, - 'uid' => $uid)) + 'uid' => $uid), + false, false, true) ); if (!empty($this->headers->structure)) { diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php index 5df672112..03ff4c116 100644 --- a/program/lib/Roundcube/rcube_output.php +++ b/program/lib/Roundcube/rcube_output.php @@ -190,6 +190,11 @@ abstract class rcube_output // Request browser to disable DNS prefetching (CVE-2010-0464) header("X-DNS-Prefetch-Control: off"); + + // send CSRF and clickjacking protection headers + if ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')) { + header('X-Frame-Options: ' . $xframe); + } } /** diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc index 859d0f096..b056a3e74 100644 --- a/program/steps/addressbook/export.inc +++ b/program/steps/addressbook/export.inc @@ -21,6 +21,8 @@ +-----------------------------------------------------------------------+ */ +$RCMAIL->request_security_check(rcube_utils::INPUT_GET); + // Use search result if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) { $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name'); diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 6a70315c7..f81bdc575 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -129,6 +129,10 @@ else if (strlen($part_id)) { exit; } + // require CSRF protected url for downloads + if ($plugin['download']) + $RCMAIL->request_security_check(rcube_utils::INPUT_GET); + // overwrite modified vars from plugin $mimetype = $plugin['mimetype']; $extensions = rcube_mime::get_mime_extensions($mimetype); diff --git a/program/steps/mail/viewsource.inc b/program/steps/mail/viewsource.inc index 1198505ab..1e1fe263e 100644 --- a/program/steps/mail/viewsource.inc +++ b/program/steps/mail/viewsource.inc @@ -19,6 +19,10 @@ +-----------------------------------------------------------------------+ */ +if (!empty($_GET['_save'])) { + $RCMAIL->request_security_check(rcube_utils::INPUT_GET); +} + ob_end_clean(); // similar code as in program/steps/mail/get.inc -- cgit v1.2.3