From e02694c3a6dbe753c5683d201b6b6b14c2b30660 Mon Sep 17 00:00:00 2001 From: thomascube Date: Sat, 10 Dec 2011 14:16:31 +0000 Subject: Backported CSS sanitization (r5586:r5590) --- CHANGELOG | 1 + program/include/main.inc | 34 ++++++++++++++++++++++++---------- program/lib/washtml.php | 12 ++++++++++-- program/steps/mail/func.inc | 16 ++++++++++------ 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2ee37fb1c..c872df378 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ CHANGELOG Roundcube Webmail RELEASE 0.7 ----------- +- Make Roundcube render the Email Standards Project Acid Test correctly - Replace prompt() with jQuery UI dialog (#1485135) - Fix navigation in messages search results - Improved handling of some malformed values encoded with quoted-printable (#1488232) diff --git a/program/include/main.inc b/program/include/main.inc index 714764959..d40dbda9a 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -883,23 +883,37 @@ function rcmail_get_edit_field($col, $value, $attrib, $type='text') * @param string Container ID to use as prefix * @return string Modified CSS source */ -function rcmail_mod_css_styles($source, $container_id) +function rcmail_mod_css_styles($source, $container_id, $allow_remote=false) { $last_pos = 0; $replacements = new rcube_string_replacer; // ignore the whole block if evil styles are detected - $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source)); - if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped)) + $source = rcmail_xss_entity_decode($source); + $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); + $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); + if (preg_match("/$evilexpr/i", $stripped)) return '/* evil! */'; - // remove css comments (sometimes used for some ugly hacks) - $source = preg_replace('!/\*(.+)\*/!Ums', '', $source); - // cut out all contents between { and } - while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) - { - $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1))); + while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { + $styles = substr($source, $pos+1, $pos2-($pos+1)); + + // check every line of a style block... + if ($allow_remote) { + $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); + foreach ($a_styles as $line) { + $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); + // ... and only allow strict url() values + if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) { + $a_styles = array('/* evil! */'); + break; + } + } + $styles = join(";\n", $a_styles); + } + + $key = $replacements->add($styles); $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2); $last_pos = $pos+2; } @@ -937,7 +951,7 @@ function rcmail_xss_entity_decode($content) { $out = html_entity_decode(html_entity_decode($content)); $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out); - $out = preg_replace('#/\*.*\*/#Um', '', $out); + $out = preg_replace('#/\*.*\*/#Ums', '', $out); return $out; } diff --git a/program/lib/washtml.php b/program/lib/washtml.php index 9c8625f30..f8c3251ad 100644 --- a/program/lib/washtml.php +++ b/program/lib/washtml.php @@ -168,7 +168,7 @@ class washtml || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])) { $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')'; } - else if (preg_match('/^(http|https|ftp):.*$/i', $match[2], $url)) { + else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) { if ($this->config['allow_remote']) $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')'; else @@ -243,7 +243,7 @@ class washtml case XML_ELEMENT_NODE: //Check element $tagName = strtolower($node->tagName); if ($callback = $this->handlers[$tagName]) { - $dump .= call_user_func($callback, $tagName, $this->wash_attribs($node), $this->dumpHtml($node)); + $dump .= call_user_func($callback, $tagName, $this->wash_attribs($node), $this->dumpHtml($node), $this); } else if (isset($this->_html_elements[$tagName])) { $content = $this->dumpHtml($node); @@ -301,6 +301,14 @@ class washtml return $this->dumpHtml($node); } + /** + * Getter for config parameters + */ + public function get_config($prop) + { + return $this->config[$prop]; + } + } ?> diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index b0a1e95bd..e51c2569b 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -821,7 +821,7 @@ function rcmail_plain_body($body, $flowed=false) /** * Callback function for washtml cleaning class */ -function rcmail_washtml_callback($tagname, $attrib, $content) +function rcmail_washtml_callback($tagname, $attrib, $content, $washtml) { switch ($tagname) { case 'form': @@ -833,8 +833,11 @@ function rcmail_washtml_callback($tagname, $attrib, $content) $stripped = preg_replace('/[^a-zA-Z\(:;]/', '', rcmail_xss_entity_decode($content)); // now check for evil strings like expression, behavior or url() - if (!preg_match('/expression|behavior|url\(|import[^a]/', $stripped)) { - $out = html::tag('style', array('type' => 'text/css'), $content); + if (!preg_match('/expression|behavior|javascript:|import[^a]/i', $stripped)) { + if (!$washtml->get_config('allow_remote') && stripos($stripped, 'url(')) + $washtml->extlinks = true; + else + $out = html::tag('style', array('type' => 'text/css'), $content); break; } @@ -1014,7 +1017,7 @@ function rcmail_message_body($attrib) $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html'])); if ($part->ctype_secondary == 'html') { - $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs); + $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs, $safe_mode); $div_attr = array('class' => 'message-htmlpart'); $style = array(); @@ -1088,7 +1091,7 @@ function rcmail_resolve_base($body) /** * modify a HTML message that it can be displayed inside a HTML page */ -function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null) +function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null, $allow_remote=false) { $last_style_pos = 0; $body_lc = strtolower($body); @@ -1101,7 +1104,7 @@ function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null // replace all css definitions with #container [def] $styles = rcmail_mod_css_styles( - substr($body, $pos, $pos2-$pos), $cont_id); + substr($body, $pos, $pos2-$pos), $cont_id, $allow_remote); $body = substr($body, 0, $pos) . $styles . substr($body, $pos2); $body_lc = strtolower($body); @@ -1279,6 +1282,7 @@ function rcmail_address_string($input, $max=null, $linked=false, $addicon=null) 'href' => "#add", 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($string)), 'title' => rcube_label('addtoaddressbook'), + 'class' => 'rcmaddcontact', ), html::img(array( 'src' => $CONFIG['skin_path'] . $addicon, -- cgit v1.2.3