Author: Badcode, sebao (知道创宇404安全实验室)
Date: 2017-03-17
Roundcube 是一款被广泛使用的开源的电子邮件程序,在全球范围内有很多组织和公司都在使用。 在服务器上成功安装 Roundcube 之后,它会提供给用户一个web 接口,通过验证的用户就可以通过 Web 浏览器收发电子邮件。 1.1.8 版本之前和 1.2.4 版本之前的 Roundcube 邮件正文展示中存在存储型跨站脚本漏洞。官方已发布升级公告。
1.1.x < 1.1.8 1.2.x < 1.2.4
Payload来自.mario
使用telnet 以html的形式发送一封包含 payload 的邮件到目标邮箱中,或者直接使用Roundcube发送一份html的邮件,发送的时候用burpsuite抓包将_message
的值改成payload即可(也可使用其他方式发送邮件, 要注意的是 payload 是否会被转义)。
当用户查看邮件即可触发跨站脚本漏洞。
首先看一下整个处理的流程,当用户查看邮件即action=show
时,index.php
会包含/program/steps/mail/show.inc
,这里会调用$OUTPUT->send('message',false)
,跳到/program/include/rcmail_output_html.php
,在前端的输出页面就是在这里产生的。在这里会调用xml_command()
这个函数循环遍历模板输出所需的数据(label,button,message headers,message body等),拼接返回。其中取message body时会跳到rcmail_message_body
函数。
先看下rcmail_message_body
函数,在/program/steps/mail/fun.inc
1169-1329行。
function rcmail_message_body($attrib)
{
......
// fetch part body
$body = $MESSAGE->get_part_body($part->mime_id, true);
// message is cached but not exists (#1485443), or other error
if ($body === false) {
rcmail_message_error($MESSAGE->uid);
}
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix',
array('part' => $part, 'prefix' => ''));
$body = rcmail_print_body($body, $part, array('safe' => $safe_mode, 'plain' => !$RCMAIL->config->get('prefer_html')));
if ($part->ctype_secondary == 'html') {
$container_id = 'message-htmlpart' . (++$part_no);
$body = rcmail_html4inline($body, $container_id, 'rcmBody', $attrs, $safe_mode);
$div_attr = array('class' => 'message-htmlpart', 'id' => $container_id);
$style = array();
if (!empty($attrs)) {
foreach ($attrs as $a_idx => $a_val)
$style[] = $a_idx . ': ' . $a_val;
if (!empty($style))
$div_attr['style'] = implode('; ', $style);
}
$out .= html::div($div_attr, $plugin['prefix'] . $body);
}
else
$out .= html::div('message-part', $plugin['prefix'] . $body);
}
}
}
......
return html::div($attrib, $out);
}
ctype_secondary的值是邮件的Content-type的第二部分(text/html),当ctype_secondary为html时,也就是邮件是html类型的,会调用rcmail_html4inline
函数对邮件的内容进行处理。这就是为什么要以html的形式发送邮件的原因。跟进到rcmail_html4inline
函数
function rcmail_html4inline($body, $container_id, $body_class='', &$attributes=null, $allow_remote=false)
{
$last_style_pos = 0;
$cont_id = $container_id . ($body_class ? ' div.'.$body_class : '');
// find STYLE tags
while (($pos = stripos($body, '<style', $last_style_pos)) && ($pos2 = stripos($body, '</style>', $pos))) {
$pos = strpos($body, '>', $pos) + 1;
$len = $pos2 - $pos;
// replace all css definitions with #container [def]
$styles = substr($body, $pos, $len);
$styles = rcube_utils::mod_css_styles($styles, $cont_id, $allow_remote);
$body = substr_replace($body, $styles, $pos, $len);
$last_style_pos = $pos2 + strlen($styles) - $len;
}
......
return $body;
可以看到,当邮件内容包含style标签时,会把style标签内的内容当成css样式,调用mod_css_styles
函数处理,过滤css样式中的危险标签。这也是整个漏洞触发的关键点,style标签的价值就体现在这。继续跟进
public static function 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
$source = self::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! */';
}
......
return $source;
}
到了此处,可以看到,对$source
调用了xss_entity_decode
函数处理,后面的代码都是对$source
进行判断和过滤。
public static function xss_entity_decode($content)
{
$out = html_entity_decode(html_entity_decode($content));
$out = preg_replace_callback('/\\\([0-9a-f]{4})/i',
array(self, 'xss_entity_decode_callback'), $out);
$out = preg_replace('#/\*.*\*/#Ums', '', $out);
return $out;
}
xss_entity_decode
对css的内容进行解码,对<img/src=x onerror=alert(1)//
进行解码,返回<img/src=x onerror=alert(1)//
原路返回,返回到rcmail_message_body
函数,此时$body
的内容如下,最后返回到rcmail_out_html.php
,拼接其他数据输出,包含payload的页面就被完整的输出到前端。
$content
被解码之后,对返回前的$out
使用strip_tags
过滤,去除 HTML 和 PHP 标签。
升级程序:https://roundcube.net/news/2017/03/10/updates-1.2.4-and-1.1.8-released
https://www.seebug.org/vuldb/ssvid-92784
http://www.securityfocus.com/bid/96817/info
http://seclists.org/oss-sec/2017/q1/583
https://twitter.com/0x6D6172696F/status/841204796887564288