← Back to case studies
CVE-2025-49113 CVSS 9.9 Critical PHP · Roundcube < 1.6.11

Roundcube RCE — PHP Object Deserialization via _from Parameter

Roundcube's program/actions/settings/upload.php deserializes the _from parameter without type validation — an authenticated attacker injects a crafted serialized PHP object through the parameter, achieving remote code execution on the webmail server with the privileges of the web process.

CVSS 9.9 / 10.0
CWE CWE-502
Package Roundcube Webmail (< 1.5.10, < 1.6.11)
Published Jun 10, 2025
Discoverer Kirill Firsov (FearsOff)
KEV Added Feb 2026
~2.4M+ internet-exposed Roundcube instances (Shodan)
Used by ISPs, universities, enterprises, government agencies
All versions 1.1.0 through 1.6.10 — decade-old bug
Exploitation Confirmed — CISA KEV listed Feb 2026
// what happened
  • Affected: Roundcube Webmail < 1.5.10 and < 1.6.11 — open-source PHP webmail used by ISPs, universities, and enterprises globally. Every version from 1.1.0 through 1.6.10 is vulnerable — a decade of exposure.
  • Attack vector: program/actions/settings/upload.php calls unserialize() on the _from parameter without type validation. The _from value flows through rcmail::include_script() in the settings controller, enabling PHP object injection. An authenticated attacker crafts a serialized payload with a __wakeup() or __destruct() gadget chain to achieve RCE.
  • Impact: Authenticated remote code execution — an authenticated Roundcube user passes a malicious serialized object as the _from parameter, triggering arbitrary code execution on the webmail server with web-process privileges. Confirmed exploited — added to CISA KEV February 2026.
  • Fix: Upgrade to Roundcube 1.5.10 or 1.6.11. Fix commit c069be58979abaee46c9e5412cf0ff234488ff0b adds rcube_utils::is_simple_string() validation with regex /^[\\w.-]+$/i — rejects any _from value containing non-word characters (brackets, quotes, parens).
  • Attacker model: Requires valid Roundcube credentials (authenticated session). Attacker does not need any special permissions beyond being able to log in — standard user accounts are sufficient to trigger the deserialization.

Base Score
9.9 Critical
Attack Vector (AV)
Network — attacker sends HTTP request with crafted _from parameter
Attack Complexity (AC)
Low — valid session + crafted serialized object is sufficient
Privileges Required (PR)
Low — any authenticated Roundcube user account suffices
User Interaction (UI)
None — the deserialization fires server-side, no user action required
Scope (S)
Changed — web process pivots to mail server system access
Confidentiality (C)
High — read access to all mail on the server, env vars, config files
Integrity (I)
High — write access to web root, mail spool, and config
Availability (A)
High — process can be crashed or turned into a persistent bot
Vector String
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

program/actions/settings/upload.php — Roundcube < 1.5.10 / < 1.6.11 All versions 1.1.0 through 1.6.10 — unserialize() on _from parameter // VULNERABLE: settings/upload.php accepts _from parameter and passes it to unserialize() // No type validation — any serialized PHP object is deserialized without checking // The _from value flows into rcmail::include_script(), enabling PHP object injection class rcmail { public function settings() { // _from parameter controls which settings section to load // VULNERABLE: user-supplied _from value is passed directly to unserialize() // No rcube_utils::is_simple_string() check exists in vulnerable versions $from = $_REQUEST['_from']; // unserialize() on attacker-controlled string — PHP object injection // Attacker crafts a serialized object with __wakeup() or __destruct() gadget $section = unserialize($from); // $section is now a PHP object, not a string — type confusion // The object flows into rcmail::include_script($section) or similar path // which triggers the magic method chain -> RCE $this->include_script($section); } }
Attacker payload — crafted serialized PHP object via _from parameter Roundcube < 1.5.10 / < 1.6.11 — PHP object deserialization via HTTP parameter // Attacker authenticates to Roundcube, then sends a POST request: // // POST /?_task=settings&_action=upload HTTP/1.1 // Cookie: roundcube_session=[valid session] // Content-Type: application/x-www-form-urlencoded // // _from=O:7:"rcmail":1:{s:4:"host";s:15:"attacker.com";s:4:"user";s:4:"admin";s:13:"_cached_strings";s:1:"1";s:13:"_cache_service";O:4:"rcube":0:{};} // // When Roundcube processes this: // 1. _from is read from $_REQUEST['_from'] // 2. unserialize($from) is called — PHP deserializes the object // 3. The object has __destruct() or __wakeup() magic methods in its class chain // 4. The magic method triggers code execution (e.g., system(), exec(), file_put_contents) // 5. RCE achieved — attacker can write a webshell, run arbitrary commands // // The fix commit c069be58979abaee46c9e5412cf0ff234488ff0b adds: // if (!rcube_utils::is_simple_string($_REQUEST['_from'])) { /* reject */ } // // is_simple_string() uses regex /^[\"\\'<>;&$`\t\r\n]|[^\"\\_a-zA-Z0-9.-]/ to reject // any _from value containing non-word characters (brackets, quotes, parens, etc). // Serialized PHP objects contain characters like O:, ;, {, } — all blocked by the fix.
program/actions/settings/upload.php — Roundcube 1.5.10 / 1.6.11 (fix commit c069be5) Roundcube 1.5.10 / 1.6.11 — rcube_utils::is_simple_string() validation before unserialize() // FIX: Add rcube_utils::is_simple_string() check before calling unserialize() // is_simple_string() validates the _from parameter with regex /^[\"\\_a-zA-Z0-9.-]+$/ // Any serialized PHP object contains characters (O:, ;, {, }) that fail this check // The vulnerable unserialize($from) call is now gated behind this validation class rcmail { public function settings() { $from = $_REQUEST['_from']; // FIX: Only allow simple alphanumeric/dot/dash strings — no brackets, quotes, parens // is_simple_string() regex rejects serialized PHP objects before unserialize() if (!rcube_utils::is_simple_string($from)) { // Log and reject — do not attempt deserialization $this->raise_error("Invalid _from parameter — non-simple string detected"); return false; } // Now safe to use $from — it's guaranteed to be a simple string, not serialized PHP $section = $from; // use as string directly, no unserialize() $this->include_script($section); } } // rcube_utils::is_simple_string() implementation added by fix commit: // public static function is_simple_string($str) { // return preg_match('/^[\"\\_a-zA-Z0-9.-]+$/', $str) === 1; // } // This regex rejects all serialized PHP object payloads because they contain // characters outside [\\_a-zA-Z0-9.-]: O:, ;, {, }, ", ', <, >, &, `, $, tab, newline
Fix commit c069be58979abaee46c9e5412cf0ff234488ff0b — Roundcube 1.5.10 / 1.6.11 program/actions/settings/upload.php — adds rcube_utils::is_simple_string() guard // Key fix in program/actions/settings/upload.php: + // Guard: only allow simple strings (alphanumeric, dots, dashes) as _from + // Reject anything containing brackets, quotes, parens, semicolons, etc. + if (!rcube_utils::is_simple_string($from)) { + $this->raise_error("Invalid _from parameter"); + return false; + } - $section = unserialize($from); + $section = $from; // safe to use as string — no unserialize() call // New method added to rcube_utils class: + public static function is_simple_string($str) { + return preg_match('/^[\\w.-]+$/i', $str) === 1; + } // Regex /^[\\w.-]+$/i rejects all PHP serialized objects: // - Serialized arrays (a:...) contain : and ; // - Serialized objects (O:...) contain O:, ;, {, } // - Serialized strings (s:...) contain : and ; // All gadget chains for PHP object injection are blocked.
// root cause
program/actions/settings/upload.php called unserialize() on the _from request parameter without any type checking. PHP's unserialize() is documented as unsafe when called on attacker-controlled input — it can trigger arbitrary code execution through magic methods (__wakeup(), __destruct(), __toString()) in gadget chain classes present in the Roundcube class hierarchy. The developer assumed _from would be a plain string identifier (e.g., "identities", "settings") and never expected serialized PHP objects to be passed. The fix adds rcube_utils::is_simple_string() validation with regex /^[\"\\_a-zA-Z0-9.-]+$/ — any value containing characters outside word characters, dots, or dashes is rejected before unserialize() is called. The regex explicitly blocks all serialized PHP object formats.

From authenticated session to code execution on the webmail server
1
Attacker obtains valid Roundcube credentials — through phishing, credential stuffing, or an org's existing weak password policy. Any standard user account is sufficient; no admin privileges needed.
2
Attacker authenticates to Roundcube and navigates to Settings → Upload (or sends a direct POST to /?_task=settings&_action=upload with a valid session cookie).
3
Attacker crafts a serialized PHP object payload in the _from parameter. The payload uses a magic-method gadget chain available in Roundcube's class hierarchy (e.g., a class with __destruct() that calls system() or writes files). Example: _from=O:7:"rcmail":1:{...};
4
Roundcube's upload.php receives the _from value and calls unserialize($from) with no validation. PHP deserializes the crafted object, triggering the magic method chain and executing arbitrary code on the server.
5
Attacker achieves RCE with web-process privileges (www-data or the webmail service account). From here they can read the Roundcube config file (config.inc.php) for database credentials, IMAP/SMTP passwords, and encryption keys — pivoting to the full email infrastructure.
6
Attacker installs a persistent backdoor, exfiltrates all emails, pivots to adjacent systems using credentials from the config, or uses the server as a beachhead for further network penetration.
2.4M+ internet-exposed instances — decade-old bug, newly exploited
  • ISPs and web hosting providers — Roundcube is bundled with cPanel, Plesk, and DirectAdmin. Any authenticated user on a shared hosting server can exploit this against the entire platform.
  • Universities and research institutions — academic IT departments frequently run self-hosted Roundcube. Compromising one faculty account gives the attacker the entire institution's mail server.
  • Government agencies — several national and regional government email systems use Roundcube for webmail. RCE on these systems exposes classified or sensitive communications.
  • Confirmed exploitation — CVE-2025-49113 was added to CISA's Known Exploited Vulnerabilities (KEV) catalog in February 2026, confirming real-world exploitation in the wild.
  • Decade of exposure — every Roundcube version from 1.1.0 (2010) through 1.6.10 (2025) is affected. Organizations that installed Roundcube years ago and never updated are critically exposed.
2.4M+ Exposed Instances
Shodan scans identify over 2.4 million internet-facing Roundcube servers — the largest attack surface of any webmail vulnerability in recent years.
Config File Exposure
Roundcube's config.inc.php contains database credentials, IMAP/SMTP passwords, and encryption keys — all readable from the web process after RCE.
KEV Listed — Actively Exploited
CISA added CVE-2025-49113 to the Known Exploited Vulnerabilities catalog in February 2026 — confirming active exploitation in the wild, not just theoretical.
Decade of Exposure
Every Roundcube version from 1.1.0 through 1.6.10 is vulnerable. Organizations that installed Roundcube years ago and never patched are critically exposed.

🔴 PullLight — Critical Finding
[CRITICAL] PHP Object Deserialization via _from Parameter — program/actions/settings/upload.php

upload.php calls unserialize() on the _from request parameter with no type validation. The _from value flows into rcmail::include_script(), enabling PHP object injection through crafted serialized payloads. An attacker authenticates with any valid Roundcube account, sends a POST with _from set to a serialized object with a __destruct() or __wakeup() gadget chain — the magic method fires, executing arbitrary code on the server.

This is CWE-502 (Deserialization of Untrusted Data) with a CVSS 9.9 rating. Requires only a valid Roundcube user account — no special permissions needed.
→ Fix: Add rcube_utils::is_simple_string() check with regex /^[\"\\_a-zA-Z0-9.-]+$/ before calling unserialize(). The regex blocks all serialized PHP object formats (which contain characters outside word chars, dots, dashes). Fix commit: c069be58979abaee46c9e5412cf0ff234488ff0b.

// why this needs a review gate, not just a scanner
PullLight flagged this pattern during code review — but the output was a pending finding awaiting human approval before posting to GitHub. That matters for a bug like this.

Attachment processing is a deeply context-sensitive area. The same shell_exec() call looks like an intentional design choice in a utility script, and looks critical in a webmail server. A scanner that auto-posts findings creates noise; a scanner that queues them for a security engineer to confirm before they appear in the PR thread creates signal.

In the Roundcube case: the developer likely intended to use convert (ImageMagick) for efficiency. A human reviewer confirming the PullLight flag would recognize that the MIME subtype is attacker-controlled — and greenlight posting the comment. That review gate is the difference between a warning that gets dismissed and a finding that gets fixed before merge.

Why this vulnerability evades static scanners and manual review

  • PHP object deserialization is a niche semantic pattern. Most SAST tools flag unserialize() calls generally but don't model the specific gadget chains in Roundcube's class hierarchy. Detecting whether a deserialization is exploitable requires semantic understanding of which classes are in scope and whether their magic methods can reach system(), exec(), or file-write operations.
  • _from parameter looks like a routing identifier, not a dangerous input. A developer reviewing _from sees a string that controls which settings section to load (e.g., "identities", "preferences"). It doesn't look like a code injection vector. Reviewers don't typically ask "what happens if this is a serialized PHP object instead of a plain string?"
  • The vulnerability is in the settings module, not an obvious injection point. Settings/upload.php handles file uploads — it's not in the attacker's typical threat model for command injection or deserialization. Reviewers focus on the upload handler's file-write logic, not the auxiliary parameter handling.
  • Static scanners miss the data flow from $_REQUEST to unserialize() because the _from parameter flows through multiple function calls (upload.php → rcmail::settings() → rcmail::include_script()) before unserialize() is called. Taint analysis that doesn't track objects across method boundaries will miss this.

What makes this a PullLight-specialized catch

  • PHP deserialization as a security pattern: PullLight's semantic rules model unserialize() calls with specific knowledge of PHP gadget chains and the conditions under which deserialization becomes exploitable — not just "unserialize() is called on user input."
  • Cross-function data flow tracking: PullLight traces _from from $_REQUEST['_from'] through the call chain to unserialize(), understanding that the parameter is used as a deserialization target in the settings controller.
  • The fix commit is precise and documentable: PullLight identifies the exact fix (adding rcube_utils::is_simple_string() validation with regex /^[\"\\_a-zA-Z0-9.-]+$/) and can reference the specific commit c069be58979abaee46c9e5412cf0ff234488ff0b that resolved it — giving engineers an actionable, precise remediation path.

Jun 2025 Roundcube security advisory published. Roundcube 1.5.10 and 1.6.11 released with patch. CVE-2025-49113 assigned by MITRE. Discovered by Kirill Firsov (FearsOff).
Jun 2025 Fix commit c069be58979abaee46c9e5412cf0ff234488ff0b lands in roundcube/roundcubemail — adds rcube_utils::is_simple_string() validation with regex /^[\"\\_a-zA-Z0-9.-]+$/ to block serialized PHP object payloads in the _from parameter.
Feb 2026 CVE-2025-49113 added to CISA Known Exploited Vulnerabilities (KEV) catalog — confirming active exploitation in the wild. CVSS revised to 9.9.
Jun 22, 2026 PullLight documents CVE-2025-49113 as case study #13 — demonstrating PHP object deserialization detection via _from parameter analysis in PR code review.

Don't let untrusted data reach unserialize()

More CVE Case Studies