From c14b337450bb546f5c1b18b1a66481844a3e79d0 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Tue, 27 Nov 2012 10:25:42 -0500 Subject: [PATCH] Fix XSS vulnerability using Flash files (#1488828) by comparing mimetypes and filename extensions --- program/localization/en_US/messages.inc | 1 program/steps/mail/func.inc | 2 program/localization/en_US/labels.inc | 1 program/steps/mail/get.inc | 72 ++++++++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index c8cbf18..abb0dca 100644 --- a/program/localization/en_US/labels.inc +++ b/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'; diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc index a900fae..68cf314 100644 --- a/program/localization/en_US/messages.inc +++ b/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.'; ?> diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index ff442ad..cb1a5dd 100644 --- a/program/steps/mail/func.inc +++ b/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); } diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 2397358..71a5e1b 100644 --- a/program/steps/mail/get.inc +++ b/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 { -- Gitblit v1.9.1