Security News

Cybersecurity news aggregator

🔓
CRITICAL Vulnerabilities Web Discovery

CVE‑2025‑49113 – Post‑Auth Remote Code Execution in Roundcube via PHP Object Deserialization

A critical remote code execution vulnerability (CVE-2025-49113
Read Full Article →

Overview A critical vulnerability has been discovered in Roundcube Webmail (versions < 1.5.10 and 1.6.0–1.6.10 ) that allows authenticated users to perform remote code execution through a PHP object deserialization flaw triggered by improper validation of the _from parameter in program/actions/settings/upload.php . The flaw carries a CVSS 3.1 score of 9.9 (Critical) CVE ID : CVE-2025-49113 Severity : Critical CVSS Score : 9.9 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) EPSS Score : 0.00661 EPSS Percentile : %70 (Likely to be exploited) Published : June 1, 2025 Affected Versions : All versions prior to 1.5.10, 1.6.11 Patched Versions : 1.5.10, 1.6.11 Technical Breakdown A patch was introduced in Roundcube with version 1.6.11 by the Roundcube team. https://github.com/roundcube/roundcubemail/commit/0376f69e958a8fef7f6f09e352c541b4e7729c4d A sanitization on the parameter _from was added to prevent it containing invalid characters such as . , – etc. // 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); } We can use the vulnerable version to find the sinkhole, Version 1.6.10, https://github.com/roundcube/roundcubemail/blob/1.6.10/ File program/actions/settings/upload.php contains the following code, $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); User controlled parameter _from is assigned to $from variable. $from is then used to remove add- or edit- prefix from the string. $type is then used to append the file to the session $rcmail->session->append($type . '.files', $id, $attachment); append method of rcube_session calls reload() method on the session object if it is not reloaded in the last 0.5 seconds. 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 method calls session_decode and eventually adds the appended data to session by doing $_SESSION = array_merge_recursive($_SESSION, $merge_data); 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); There are custom overrides for serialize and unserialize functions in Roundcube which makes the exploitation even harder. /** * 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; } Serialized objects are splitted by | character in the session. Unserialize function wrongly handles the ! character resulting in a session corruption. /** * 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; } Cyber Researcher Kirill Firsov, discovered this bug, has an explanation about how he found and exploited the bug in his blog . Exploitation Steps Exploitation of this vulnerability is not easy. It requires a custom PHP object deserialization and laying the payload into two different parameters, _from and filename . One can use Crypt_GPG_Engine class to craft an object deserialization gadget. A private member of the class _gpgconf is used in the proc_open function so it can be updated to execute a command that the attacker controls. private function _closeIdleAgents() // N

Share this article