summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bruederli <thomas@roundcube.net>2012-11-27 16:25:42 +0100
committerThomas Bruederli <thomas@roundcube.net>2012-11-27 16:25:42 +0100
commitc14b337450bb546f5c1b18b1a66481844a3e79d0 (patch)
tree1e4307f9ac1e3c634f51531ce025e493641e5454
parentdff2c713fbc5dd8a501a31f3c158b8be412f46d0 (diff)
Fix XSS vulnerability using Flash files (#1488828) by comparing mimetypes and filename extensions
-rw-r--r--program/localization/en_US/labels.inc1
-rw-r--r--program/localization/en_US/messages.inc1
-rw-r--r--program/steps/mail/func.inc2
-rw-r--r--program/steps/mail/get.inc72
4 files changed, 71 insertions, 5 deletions
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index c8cbf1841..abb0dca5d 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -64,6 +64,7 @@ $labels['move'] = 'Move';
$labels['moveto'] = 'Move to...';
$labels['download'] = 'Download';
$labels['showattachment'] = 'Show';
+$labels['showanyway'] = 'Show it anyway';
$labels['filename'] = 'File name';
$labels['filesize'] = 'File size';
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index a900fae18..68cf314e7 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -163,6 +163,7 @@ $messages['invalidimageformat'] = 'Not a valid image format.';
$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.';
?>
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index ff442ad60..cb1a5ddae 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1664,7 +1664,7 @@ function rcmail_message_part_frame($attrib)
$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);
}
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index 2397358a1..71a5e1b02 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -112,6 +112,71 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
// 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 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
$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']) {
@@ -144,6 +207,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
header("Content-Transfer-Encoding: binary");
}
+
// deliver part content
if ($ctype_primary == 'text' && $ctype_secondary == 'html' && empty($plugin['download'])) {
// Check if we have enough memory to handle the message in it
@@ -166,7 +230,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
check_storage_status();
}
- $OUTPUT = new rcube_output_html();
+ $OUTPUT = new rcube_html_page();
$OUTPUT->write($out);
}
else {