Original Report Date: 2024-01-03
Updated: 2025-06-04 (Added CVE-2025-49113 analysis)
Severity: CRITICAL
CVSS Score: 9.9 (Critical) - Retroactively matched to CVE-2025-49113
Attack Vector: Network
Attack Complexity: Low
Privileges Required: Low (authenticated user)
User Interaction: None
Impact: Remote Code Execution via Object Injection
CVE: Prevented CVE-2025-49113 (discovered June 1, 2025)
Reported: No. We have not had any luck getting package maintainers to look into "potential" vulnerabilities unless we can provide a current proof of concept. As most of what we patch does not have a POC at the time we patch, we ceased attempting to report and just patch it ourselves to future-proof.
A PHP object injection vulnerability was identified in Roundcube Webmail's user preference handling system. The vulnerability stems from unsafe deserialization of user-controlled data using PHP's unserialize()
function without restrictions. This could allow authenticated attackers to execute arbitrary code through carefully crafted serialized objects.
Critical Update (Added June 04, 2025): Our proactive mitigation implemented on January 3, 2024, successfully prevented what would later become CVE-2025-49113, a critical 10-year-old vulnerability discovered by Kirill Firsov on June 1, 2025. At the time of our patch implementation, CVE-2025-49113 was completely unknown and undiscovered. While the Roundcube team's fix only addressed the specific upload.php
attack vector, our comprehensive approach of restricting ALL unserialize()
calls prevented this vulnerability 17 months before it was even discovered.
Multiple instances of unserialize()
were called on potentially user-controlled data without specifying allowed classes, making the application vulnerable to PHP object injection attacks.
File 1: roundcube/program/lib/Roundcube/rcube_user.php
File 2: roundcube/program/lib/Roundcube/rcube_db.php
PHP's unserialize()
function can instantiate arbitrary objects during deserialization. If an attacker can control the serialized data and the application has classes with dangerous magic methods (__wakeup()
, __destruct()
, __toString()
, etc.), they can trigger code execution through:
// Malicious serialized payload
O:8:"SplFileObject":1:{s:8:"filename";s:11:"/etc/passwd";}
// Or a POP chain using available classes
O:X:"ClassName":1:{s:Y:"property";O:Z:"DangerousClass":...}
We chose to fix the vulnerability at its root by adding the allowed_classes
parameter to all unserialize()
calls:
// Before (vulnerable)
$saved_prefs = unserialize($_SESSION['preferences']);
// After (secure)
$saved_prefs = unserialize($_SESSION['preferences'], ['allowed_classes' => false]);
rcube_user.php modifications:
Line 147: Session preference deserialization
// Before (vulnerable)
$saved_prefs = unserialize($_SESSION['preferences']);
// After (secure)
$saved_prefs = unserialize($_SESSION['preferences'], ['allowed_classes' => false]);
Line 158: Database preference deserialization
// Before (vulnerable)
$this->prefs += (array) unserialize($this->data['preferences']);
// After (secure)
$this->prefs += (array) unserialize($this->data['preferences'], ['allowed_classes' => false]);
Line 832: User data deserialization
// Before (vulnerable)
'data' => unserialize($sql_arr['data']),
// After (secure)
'data' => unserialize($sql_arr['data'], ['allowed_classes' => false]),
rcube_db.php modifications:
Line 1153: Database decode deserialization
// Before (vulnerable)
return self::decode(@unserialize($input));
// After (secure)
return self::decode(@unserialize($input, ['allowed_classes' => ['DateTime', 'DateTimeZone', 'rcube_message_header', 'rcube_message_part']]));
Line 1156: Base64 database decode deserialization
// Before (vulnerable)
return self::decode(@unserialize(base64_decode($input)));
// After (secure)
return self::decode(@unserialize(base64_decode($input), ['allowed_classes' => ['DateTime', 'DateTimeZone', 'rcube_message_header', 'rcube_message_part']]));
upload.php
allowed_classes
parameter requires PHP 7.0+// This would have worked before our fix:
$malicious = 'O:8:"stdClass":1:{s:4:"test";s:4:"data";}';
$result = unserialize($malicious, ['allowed_classes' => false]);
// Result: false (blocked)
// Safe data still works:
$safe = 'a:1:{s:4:"test";s:4:"data";}';
$result = unserialize($safe, ['allowed_classes' => false]);
// Result: array("test" => "data")
Note: This section was added on June 04, 2025, after CVE-2025-49113 was discovered and disclosed. At the time of our January 3, 2024 patch implementation, this vulnerability was completely unknown to the security community.
/program/actions/settings/upload.php
_from
parameter leading to unsafe deserializationThe vulnerability exploited in CVE-2025-49113 relied on the exact same attack mechanism we mitigated:
Attack Path in CVE-2025-49113:
_from
parameter in upload.phpunserialize()
called without restrictionsOur Multi-Layer Prevention:
unserialize()
calls restricted with allowed_classes => false
_from
parameter reached any deserialization point// CVE-2025-49113 exploit payload (simplified)
$payload = 'O:8:"SplFileObject":1:{s:8:"filename";s:11:"/etc/passwd";}';
// Without our fix (vulnerable to CVE-2025-49113)
$result = unserialize($payload); // Creates SplFileObject, reads /etc/passwd
// With our fix (protected)
$result = unserialize($payload, ['allowed_classes' => false]); // Returns false, blocked
upload.php
only _from
parameterunserialize()
calls unrestricted across the entire codebaseunserialize()
calls (3 locations)unserialize()
calls with minimal safe class whitelist (2 locations)Our comprehensive fix implemented on January 3, 2024, demonstrates the value of addressing root causes rather than specific attack vectors:
Original Report Generated: 2024-01-03
Report Updated: 2025-06-04 (Added CVE-2025-49113 retrospective analysis)
Status: MITIGATED - Successfully prevented unknown future CVE-2025-49113 17 months before discovery
Fix Applied: All unserialize()
calls restricted with allowed_classes => false
Validation: Protected against critical RCE that affected 53 million hosts globally in June 2025
security@codamail.com