漏洞概述
近日官方披露 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
的格式检查,将其限定为简单字符串类型:
请求路由分析
首先分析一下到达 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
满足不了这种格式要求 :
往下看还有另一种获取 $class
的方式: rcmail_action_{$task}_{$action}
,如果类存在将调用其 run
函数:
需要注意在调用 run
之前存在 check
检查:
可以在请求中加入 _remote
参数,确保 $rcmail->output->ajax_call
非空从而使得 check
函数返回值为 true
:
构造如下 HTTP
请求可进入 rcmail_action_settings_upload::run
:
GET /?_task=settings&_action=upload&_remote=1&_from=from123...
调用栈如下:
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
:
继续往下走顺利进入 rcube_session::unserialize
。调试发现传入 PHP
原生反序列化函数 unserialize
的参数 $serialized
中,含有 filename
和 _from
的值:
可以通过控制请求参数来污染 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
反序列化函数中主要新增了对 !
和 |
等字符的处理:
分析后得出如下结论:
-
• |
符号类似一个分割符,将对象分为key
和value
; -
• !
符号会将value
设置为N;
,表示NULL
空值。
在 _from
和 filename
这两个可污染 session
的参数中尝试加入 |
和 !
符号,发现当构造特定请求时, filename
中的字符串被转换为一个序列化对象:
该序列化对象最后会二次调用 PHP
原生 unserialize
函数,找一个 Roundcube
中的类进行测试,比如 rcmail_sendmail
,成功进入 rcmail_sendmail::__destruct
:
如果换成其他可利用的 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
类:任意文件删除; -
• GuzzleHttpCookieFileCookieJar
类:任意文件写入; -
• GuzzleHttpPsr7FnStream
类: 任意代码执行; -
• Crypt_GPG_Engine
类:任意命令执行; -
• ...
分析发现 filename
参数存在一定限制,无法包含反斜杠等特殊字符,导致 GuzzleHttpCookieFileCookieJar
这种存在命令空间的类无法使用,好在 Crypt_GPG_Engine
没有命令空间,所以可以用来触发 RCE
(可借助 pspy
监控命令执行进程 ):
利用方式拓展
有没有更通用的利用方式呢?将 filename
后缀设置为类似 s:300
这种长字符长度时,观察到后面还有内容会写入 session
,其中就包含了请求参数 _from
的值:
因此可以通过控制字符串长度让其与 _from
进行闭合,然后将 gadget
放在 _from
后面,这样也可以触发反序列化操作,并且不存在限制问题:
如何实现文件写入
从原理上看,反序列化触发路径应该与 index.php
相同,均位于 Web
目录,但是调试发现命令执行时工作目录变成了系统根目录,因权限不够导致无法写入 shell
。分析后发现问题出在 proc_open
函数,这里参数 cwd
被设置为了 null
,导致工作目录被重置为 /
:
绕过方法非常简单,最终成功 getshell
:
原文始发于微信公众号(自在安全):潜伏十年的严重漏洞:CVE-2025-49113 Roundcube 反序列化漏洞原理与 gadget 构造深度分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论