潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析

原创 KCyber 自在安全 2025-06-07 23:30

漏洞概述

近日官方披露 Roundcube
邮件系统中存在一个潜伏了10
年之久的严重漏洞,虽然只有认证后才能触发,但CVSS
评分仍然高达9.9
分。由于Roundcube
自定义的session
序列化与反序列化函数存在逻辑缺陷,导致可通过构造特殊请求实现RCE
。该漏洞影响1.5.10
及1.6.11
 以下版本。

补丁对比

补丁地址: https://github.com/roundcube/roundcubemail/commit/0376f69e958a8fef7f6f09e352c541b4e7729c4d 。rcmail_action_settings_upload::run
 函数中新增了对请求参数 _from
 的格式检查,将其限定为简单字符串类型:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -1

请求路由分析

首先分析一下到达 rcmail_action_settings_upload::run
 的请求路由。 rcmail_action_settings_upload
 继承了 rcmail_action
 抽象类,分析可知 rcmail_action
 子类主要用于处理对应的 HTTP
 请求。

在系统首页 index.php
中将调用$RCMAIL->action_handler
进行路由分发。进入action_handler
函数,提取task
和action
变量(来自于请求参数_task
和_action
), 并尝试创建处理请求的rcmail_action
对象。函数中存在多种类创建方式,比如rcmail_action_{$task}_index
,但是显然rcmail_action_settings_upload
 满足不了这种格式要求 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -2

往下看还有另一种获取 $class
 的方式: rcmail_action_{$task}_{$action}
 ,如果类存在将调用其 run
 函数:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -3

需要注意在调用 run
 之前存在 check
 检查:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -4

可以在请求中加入 _remote
 参数,确保 $rcmail->output->ajax_call
 非空从而使得 check
 函数返回值为 true
 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -5

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -6

构造如下 HTTP
 请求可进入 rcmail_action_settings_upload::run
 :

GET /?_task=settings&_action=upload&_remote=1&_from=from123...

调用栈如下:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -7

session序列化与反序列化分析

既然是 PHP
反序列化漏洞,首先搜索一下unserialize
的调用点。Roundcube
中unserialize
函数使用的并不多,我们注意到在处理session
的rcube_session
类中,自定义了serialize
和unserialize
函数,用于完成session
会话的序列化与反序列化。反向搜索rcube_session::unserialize
调用点,找到sess_write
 函数:

rcube_session.unserialize    │        └─→ rcube_session._fixvars           │              └─→rcube_session.sess_write

直接分析找不到 sess_write
的调用点,那么如何触发呢?注意到rcube_session
初始化过程中利用session_set_save_handler
将sess_write
注册为了session
写入的回调函数,并且类中还定义了append
、remove
、reload
等函数对session
 进行处理:

public function register_session_handler(){    ...    // set custom functions for PHP session management      session_set_save_handler(          [$this, 'open'],          [$this, 'close'],          [$this, 'read'],          [$this, 'sess_write'],   //设置 sess_write 回调函数        [$this, 'destroy'],          [$this, 'gc']      );    ...}.../** * Re-read session data from storage backend */public function reload().../** * Append the given value to the certain node in the session data array * * Warning: Do not use if you already modified $_SESSION in the same request (#1490608) * * @param string $path  Path denoting the session variable where to append the value * @param string $key   Key name under which to append the new value (use null for appending to an indexed list) * @param mixed  $value Value to append to the session data array */public function append($path, $key, $value).../** * Unset a session variable * * @param string $var Variable name (can be a path denoting a certain node *                    in the session array, e.g. compose.attachments.5) * * @return bool True on success, False on failure */public function remove($var = null)

而 rcmail_action_settings_upload::run
 刚好调用了其中的 append
 函数。修改请求数据包为图片文件上传的格式,成功触发 rcube_session::append
 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -8

继续往下走顺利进入 rcube_session::unserialize
 。调试发现传入 PHP
 原生反序列化函数 unserialize
 的参数 $serialized
 中,含有 filename
 和 _from
 的值:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -9

可以通过控制请求参数来污染 session
 会话,那么是否存在利用的可能性呢?为了方便分析,新建一个辅助的 PHP
 文件,并将 rcube_session
 类中的 unserialize
 和 serialize
 放入其中(还可以在 rcube_session::unserialize
 中添加 echo
 打印信息):

<?phpclass My{    //rcube_session::serialize    protected function serialize($vars)    {        ...    }    //rcube_session::unserialize    public static function unserialize($str)    {        ...    }}$a=$_POST['payload'];$f=My::unserialize($a);echo $f;

与原生反序列化相比,自定义的 session
 反序列化函数中主要新增了对 !
 和 |
 等字符的处理:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -10

分析后得出如下结论:
– • |
 符号类似一个分割符,将对象分为 key
 和 value
 ;

  • • !
     符号会将 value
     设置为 N;
     ,表示 NULL
     空值。

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -11

在 _from
 和 filename
 这两个可污染 session
 的参数中尝试加入 |
 和 !
 符号,发现当构造特定请求时, filename
 中的字符串被转换为一个序列化对象:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -12

该序列化对象最后会二次调用 PHP
 原生 unserialize
 函数,找一个 Roundcube
 中的类进行测试,比如 rcmail_sendmail
 ,成功进入 rcmail_sendmail::__destruct
 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -13

如果换成其他可利用的 gadget
 ,就可能会触发 RCE
 。

gadget利用链构造

上面测试用的 rcmail_sendmail
 类本身就是一个可利用的 gadget
 ,可实现任意文件删除:

public function __destruct(){    foreach ($this->temp_files as $file) {        @unlink($file);    }}

Roundcube
 中可利用的反序列化 gadget
 比较多,以下是整理的部分 gadget
 :
– • rcmail_sendmail
 类 :任意文件删除;

  • • rcmail_attachment_handler
     类:任意文件删除;

  • • \GuzzleHttp\Cookie\FileCookieJar
     类:任意文件写入;

  • • \GuzzleHttp\Psr7\FnStream
     类: 任意代码执行;

  • • Crypt_GPG_Engine
     类:任意命令执行;

  • • …

分析发现 filename
 参数存在一定限制,无法包含反斜杠等特殊字符,导致 \GuzzleHttp\Cookie\FileCookieJar
 这种存在命令空间的类无法使用,好在 Crypt_GPG_Engine
 没有命令空间,所以可以用来触发 RCE
 (可借助 pspy
 监控命令执行进程 ):

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -14

利用方式拓展

有没有更通用的利用方式呢?将 filename
 后缀设置为类似 s:300
 这种长字符长度时,观察到后面还有内容会写入 session
 ,其中就包含了请求参数 _from
 的值:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -15

因此可以通过控制字符串长度让其与 _from
 进行闭合,然后将 gadget
 放在 _from
 后面,这样也可以触发反序列化操作,并且不存在限制问题:

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -16

如何实现文件写入

从原理上看,反序列化触发路径应该与 index.php
 相同,均位于 Web
 目录,但是调试发现命令执行时工作目录变成了系统根目录,因权限不够导致无法写入 shell
 。分析后发现问题出在 proc_open
 函数,这里参数 cwd
 被设置为了 null
 ,导致工作目录被重置为 /
 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -17

绕过方法非常简单,最终成功 getshell
 :

潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析 -18

由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。