Fix XSS vulnerability using Flash files (#1488828) by comparing mimetypes and filename extensions

pull/66/head
Thomas Bruederli 12 years ago
parent dff2c713fb
commit c14b337450

@ -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';

@ -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.';
?>

@ -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);
}

@ -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 {

Loading…
Cancel
Save