Thomas Bruederli
2012-11-27 c14b337450bb546f5c1b18b1a66481844a3e79d0
Fix XSS vulnerability using Flash files (#1488828) by comparing mimetypes and filename extensions
4 files modified
76 ■■■■■ changed files
program/localization/en_US/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/en_US/messages.inc 1 ●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/get.inc 72 ●●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc
@@ -64,6 +64,7 @@
$labels['moveto']   = 'Move to...';
$labels['download'] = 'Download';
$labels['showattachment'] = 'Show';
$labels['showanyway'] = 'Show it anyway';
$labels['filename'] = 'File name';
$labels['filesize'] = 'File size';
program/localization/en_US/messages.inc
@@ -163,6 +163,7 @@
$messages['mispellingsfound'] = 'Spelling errors detected in the message.';
$messages['parentnotwritable'] = 'Unable to create/move folder into selected parent folder. No access rights.';
$messages['messagetoobig'] = 'The message part is too big to process it.';
$messages['attachmentvalidationerror'] = 'WARNING! This attachment is suspicious because its type doesn\'t match the type declared in the message. If you do not trust the sender, you shouldn\'t open it in the browser because it may contain malicious contents.<br/><br/><em>Expected: $expected; found: $detected</em>';
$messages['noscriptwarning'] = 'Warning: This webmail service requires Javascript! In order to use it please enable Javascript in your browser\'s settings.';
?>
program/steps/mail/func.inc
@@ -1664,7 +1664,7 @@
  $part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
  $ctype_primary = strtolower($part->ctype_primary);
  $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
  $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_embed=' : '_preload='), $_SERVER['QUERY_STRING']);
  return html::iframe($attrib);
}
program/steps/mail/get.inc
@@ -112,6 +112,71 @@
    // overwrite modified vars from plugin
    $mimetype = $plugin['mimetype'];
    $extensions = rcube_mime::get_mime_extensions($mimetype);
    if ($plugin['body'])
      $part->body = $plugin['body'];
    // compare file mimetype with the stated content-type headers and file extension to avoid malicious operations
    if (!empty($_REQUEST['_embed']) && empty($_REQUEST['_nocheck'])) {
      $file_extension = strtolower(pathinfo($part->filename, PATHINFO_EXTENSION));
      // 1. compare filename suffix with expected suffix derived from mimetype
      $valid = $file_extension && in_array($file_extension, (array)$extensions);
      // 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension
      if ($valid || !$file_extension || $mimetype == 'application/octet-stream') {
        if ($part->body)  // part body is already loaded
          $body = $part->body;
        else if ($part->size && $part->size < 1024*1024)   // load the entire part if it's small enough
          $body = $part->body = $MESSAGE->get_part_content($part->mime_id);
        else  // fetch the first 2K of the message part
          $body = $MESSAGE->get_part_content($part->mime_id, null, true, 2048);
        // detect message part mimetype
        $real_mimetype = rcube_mime::file_content_type($body, $part->filename, $mimetype, true, true);
        list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype);
        // ignore differences in text/* mimetypes. Filetype detection isn't very reliable here
        if ($real_ctype_primary == 'text' && strpos($mimetype, $real_ctype_primary) === 0)
          $real_mimetype = $mimetype;
        // get valid file extensions
        $extensions = rcube_mime::get_mime_extensions($real_mimetype);
        $valid_extension = (!$file_extension || in_array($file_extension, (array)$extensions));
        // fix mimetype for images wrongly declared as octet-stream
        if ($mimetype == 'application/octet-stream' && strpos($real_mimetype, 'image/') === 0 && $valid_extension)
          $mimetype = $real_mimetype;
        $valid = ($real_mimetype == $mimetype && $valid_extension);
      }
      else {
        $real_mimetype = $mimetype;
      }
      // show warning if validity checks failed
      if (!$valid) {
        $OUTPUT = new rcmail_html_page();
        $OUTPUT->write(html::tag('html', null, html::tag('body', array('style' => 'font-family:sans-serif; margin:1em'),
          html::div(array('class' => 'warning', 'style' => 'border:2px solid #ffdf0e; background:#fef893; padding:1em 1em 0 1em;'),
            rcube_label(array(
              'name' => 'attachmentvalidationerror',
              'vars' => array('expected' => "$mimetype (.$file_extension)", 'detected' => "$real_mimetype (.$extensions[0])")
            )) .
            html::p('buttons',
              html::tag('button', null,
                html::a(array(
                  'href' => $RCMAIL->url(array_merge($_GET, array('_nocheck' => 1))),
                  'style' => 'text-decoration:none;color:#000',
                ), rcube_label('showanyway')))
            ))
        )));
        exit;
      }
    }
    // TIFF to JPEG conversion, if needed
    $tiff_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps']['tif']);
@@ -123,11 +188,9 @@
      $mimetype = 'image/jpeg';
    }
    list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
    if ($plugin['body'])
      $part->body = $plugin['body'];
    $browser = $RCMAIL->output->browser;
    list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
    // send download headers
    if ($plugin['download']) {
@@ -143,6 +206,7 @@
      header("Content-Type: $mimetype");
      header("Content-Transfer-Encoding: binary");
    }
    // deliver part content
    if ($ctype_primary == 'text' && $ctype_secondary == 'html' && empty($plugin['download'])) {
@@ -166,7 +230,7 @@
        check_storage_status();
      }
      $OUTPUT = new rcube_output_html();
      $OUTPUT = new rcube_html_page();
      $OUTPUT->write($out);
    }
    else {