CVE-2016-4069 (published 2016-08-25T18:59Z)

Cross-site request forgery (CSRF) vulnerability in Roundcube Webmail before 1.1.5 allows remote attackers to hijack the authentication of users for requests that download attachments and cause a denial of service (disk consumption) via unspecified vectors.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/roundcube/roundcubemail
commit_details: repo_url: https://github.com/roundcube/roundcubemail
author: Thomas Bruederli
author_date: 2016-01-16 17:48:22+01:00
msg: Protect download urls against CSRF using unique request tokens (#1490642)
Send X-Frame-Options headers with every HTTP response
repository: repo_url: https://github.com/roundcube/roundcubemail
repo_name: roundcube/roundcubemail
description: The Roundcube Webmail suite
repo_language: PHP
owner: roundcube
file_changes: filename: enigma.js
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: JavaScript
filename: enigma_ui.php
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: PHP
filename: rcube_sieve_engine.php
change_type: ModificationType.MODIFY
diff: @@ -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);
 

programming_language: PHP
filename: managesieve.js
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: JavaScript
filename: zipdownload.js
change_type: ModificationType.MODIFY
diff: @@ -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;
     }
 

programming_language: JavaScript
filename: zipdownload.php
change_type: ModificationType.MODIFY
diff: @@ -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');

programming_language: PHP
filename: rcmail.php
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: PHP
filename: rcmail_output_html.php
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: PHP
filename: app.js
change_type: ModificationType.MODIFY
diff: @@ -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)

programming_language: JavaScript
filename: rcube_message.php
change_type: ModificationType.MODIFY
diff: @@ -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)) {

programming_language: PHP
filename: rcube_output.php
change_type: ModificationType.MODIFY
diff: @@ -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);
+        }
     }
 
     /**

programming_language: PHP
filename: export.inc
change_type: ModificationType.MODIFY
diff: @@ -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');

programming_language: PHP
filename: get.inc
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: PHP
filename: viewsource.inc
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: PHP