Someone left a skeleton key in the mail room's optional lock, and burglars just figured out it works
CVE-2026-48842 is a pre-authentication SQL injection in Roundcube Webmail's virtuser_query plugin, affecting versions 1.6.x before 1.6.16 and 1.7.x before 1.7.1. The plugin maps login usernames to email addresses using a configurable SQL template. The vulnerable code used preg_replace('%u', $dbh->escape($user), $query) to substitute user input into the SQL string — but PHP's preg_replace() interprets backslash sequences in the replacement argument, meaning a crafted backslash payload can *undo* the database driver's escaping and inject arbitrary SQL. The injection point is the login form's _user field at ?_task=login, requiring zero authentication. The fix was a one-line change: swap preg_replace() for str_replace(), which does not interpret backslashes.
The vendor's HIGH / 8.1 rating with AC:H is *close to correct* but slightly overstates the population-wide risk. The critical friction is that virtuser_query is not enabled by default — it must be explicitly added to the $config['plugins'] array in config.inc.php. This means vanilla Roundcube installations are not vulnerable. However, the plugin is heavily used by ISPs and shared-hosting providers running multi-domain mail, which are exactly the deployments most likely to be internet-facing. With active exploitation confirmed by Canada's CCCS as of September 21, 2026, a public Python PoC on GitHub, and 523,000+ Roundcube instances on the internet, the vendor's HIGH is directionally right — but the non-default plugin requirement justifies a modest downgrade to 7.5 rather than a full endorsement of 8.1.
5 steps from start to impact.
Identify exposed Roundcube instance
roundcube_version meta tag, CSS/JS asset paths, or the _token generation pattern). The murrez/CVE-2026-48842 PoC automates this with python poc.py -u <target>.- Target Roundcube is internet-facing
- Attacker can reach the login page
- Version fingerprinting is not always reliable if admins strip version info
Probe for virtuser_query plugin
_user value to determine whether the virtuser_query plugin is loaded. The PoC's --verify flag sends backslash-laden usernames and analyzes response timing and error behavior to confirm the plugin is active. If the plugin is not enabled, the attack chain stops here.- virtuser_query plugin must be explicitly enabled in config.inc.php
- Plugin must be configured with a SQL query template containing
%u
- Plugin is not enabled by default — only deployments with multi-domain virtual user lookups (ISPs, shared hosting) enable it
- No public data on what fraction of 523K instances have this plugin active — likely a minority
_user patterns). IDS signatures for backslash-heavy POST payloads to ?_task=login.Exploit preg_replace backslash bypass for SQL injection
_user value containing carefully crafted backslash sequences. When preg_replace() processes the replacement string, PHP re-interprets the backslashes, stripping the escaping applied by $dbh->escape(). This allows the attacker to break out of the SQL string context and inject arbitrary SQL. The injection is blind (no direct output) but can be exploited via time-based or error-based techniques. The murrez PoC and Proofpoint-documented UNK_MassTraction campaign both weaponize this step.- Vulnerable Roundcube version (< 1.6.16 or < 1.7.1)
- virtuser_query active (from step 2)
- CVSS rates AC:H — the backslash bypass requires precise payload crafting depending on the database backend (MySQL vs PostgreSQL vs SQLite)
- WAF or mod_security rules may block the payloads
Exfiltrate credentials and email data
users table, session tokens from session, and potentially pivots to the mail server's virtual user tables if Roundcube's database user has cross-schema access. Extracted credentials enable mailbox access via IMAP/SMTP directly, bypassing Roundcube entirely. In espionage campaigns (UNK_MassTraction), the goal is harvesting email contents from high-value targets.- Successful SQL injection from step 3
- Roundcube DB user has read access to relevant tables
- Database permissions may limit lateral movement beyond Roundcube's own schema
- Credential hashing (if passwords are stored hashed) slows credential reuse
Potential RCE via database write primitives
SELECT INTO OUTFILE (MySQL), COPY TO (PostgreSQL), or UDF injection. This would give shell access on the Roundcube server, enabling web shell deployment (as seen in the UNK_MassTraction VShell campaign) and lateral movement into the mail infrastructure.- Database user has FILE privilege (MySQL) or superuser-level access (PostgreSQL)
- Writable web directory path is known
- Modern database hardening disables FILE privilege and restricts COPY TO
- AppArmor/SELinux on the DB process prevents filesystem writes
- Many Roundcube deployments use restricted DB users without write-to-disk capabilities
config/config.inc.php and remove 'virtuser_query' from the $config['plugins'] array, then restart Apache/PHP-FPM. This completely eliminates the attack surface regardless of Roundcube version. If virtual user lookups are needed, switch to the virtuser_file plugin or implement lookups at the MTA (Postfix virtual_alias_maps) layer instead. Deploy within hours given active exploitation — this is a zero-downtime change that only affects how login usernames map to email addresses.preg_replace() with str_replace() in the plugin code, eliminating the backslash interpretation vulnerability. Per the noisgate mitigation SLA for HIGH with active exploitation, patch immediately (within hours). Test in staging first — the update is a point release with minimal functional changes.?_task=login where the _user parameter contains backslash sequences (\\, \', \", \0). This is a defense-in-depth measure — not a substitute for patching — but buys time if immediate patching isn't feasible. Deploy within 24 hours as a stopgap. Note: overly aggressive rules may block legitimate usernames containing backslashes (rare but possible).db_dsnw has only SELECT, INSERT, UPDATE, DELETE on Roundcube's own schema — no FILE, SUPER, CREATE, or cross-schema privileges. This limits post-exploitation impact by preventing RCE escalation via INTO OUTFILE or COPY TO. Deploy within the 30-day noisgate mitigation SLA for HIGH.- Database-level parameterized queries won't help without code change — the vulnerability is in how PHP's
preg_replace()interprets the replacement string *after* escaping. The escaping itself is correct; PHP undoes it. Only changing tostr_replace()(the vendor fix) or removing the plugin resolves this. - Roundcube's built-in rate limiting does not prevent exploitation — the SQLi succeeds on a single crafted request, not a brute-force attack. Rate limiting only slows scanning, not exploitation.
- TLS/HTTPS alone provides no protection — the injection is in the application layer POST body, fully within the encrypted channel.
The supporting signals.
| In-the-Wild Exploitation | Active. Canada's CCCS confirmed exploitation on September 21, 2026, four months post-patch. UNK_MassTraction (suspected China-aligned) exploited Roundcube flaws in a July 2026 campaign targeting universities with ties to national security, astrophysics, and particle physics research (Proofpoint). |
|---|---|
| Proof of Concept | Public. murrez/CVE-2026-48842 — Python PoC with version detection, plugin probing, and active SQLi verification via --verify flag. Researcher: skull (original reporter to Roundcube). |
| EPSS Score | 0.00891 (~89th percentile). Moderate exploitation probability, likely to rise given confirmed in-the-wild activity. |
| KEV Status | Not listed as of 2026-09-26. Given active exploitation confirmation, KEV addition is plausible in coming weeks. |
| CVSS Vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — Network-accessible, no auth, no user interaction, but high attack complexity (requires virtuser_query enabled + precise backslash crafting). Full CIA impact on the database. |
| Affected Versions | Roundcube 1.6.0 – 1.6.15 and 1.7.0. The 1.5.x branch is EOL and *may* be affected if virtuser_query is enabled (unconfirmed by vendor). |
| Fixed Versions | 1.6.16 and 1.7.1, released May 24, 2026. Distro backports: check Debian roundcube package changelogs; Ubuntu USN tracking pending. |
| Internet Exposure | 523,000+ Roundcube instances exposed per Shadowserver (September 2026). Unknown fraction have virtuser_query enabled — likely concentrated in ISP/shared-hosting deployments. |
| Disclosure Date | May 25, 2026 (public). Patches released one day prior on May 24. |
| Reporting Researcher | skull — reported directly to Roundcube security team. |
Sources.
- Roundcube Security Update 1.6.16 / 1.7.1
- The Hacker News — Active Exploitation Report
- BleepingComputer — Critical Roundcube Flaw Exploited
- murrez/CVE-2026-48842 — Python PoC
- Roundcube virtuser_query Plugin Source (patched)
- Proofpoint — 2026 Vulnerability Exploitation Playbook
- SecurityWeek — Roundcube Vulnerability in Attackers' Crosshairs
- NVD — CVE-2026-48842 Detail
Why this verdict
- Non-default plugin is the primary friction: virtuser_query must be explicitly enabled in
config.inc.php. Vanilla Roundcube installs are *not vulnerable*. This narrows the exploitable population significantly below the 523K exposed instances, justifying a downward adjustment from the vendor's 8.1. - Active exploitation overrides theoretical friction: CCCS confirmed in-the-wild exploitation on September 21, 2026. UNK_MassTraction is already targeting Roundcube infrastructure for espionage. The attack is pre-auth and has a public PoC — the complexity barrier is lower than AC:H suggests for a motivated adversary with the murrez tool.
- Role multiplier: Roundcube occupies a *typical-role* position as a webmail frontend. It is not in the canonical high-value-role catalog (not a DC, hypervisor, IdP, PAM, or network edge). However, shared-hosting ISP deployments with virtuser_query enabled serve hundreds to thousands of mail users per instance — a successful SQLi compromises *all* user credentials on that instance (tenant-level blast radius). For government and academic deployments targeted by UNK_MassTraction, the data sensitivity is espionage-grade. This keeps the floor at HIGH but does not push to CRITICAL.
- Pre-auth network attack surface: No credentials needed. The injection point is the public login form. Any scanner can reach it. This is the strongest upward pressure and prevents dropping below HIGH despite the plugin prerequisite.
- Downward adjustment (-0.6): The non-default plugin requirement is a real-world narrowing not fully captured by AC:H. We estimate <30% of exposed Roundcube instances run virtuser_query, reducing effective exposure from 523K to roughly 100-150K instances. This justifies the modest score reduction from 8.1 to 7.5.
Why not higher?
CRITICAL would require either a canonically high-value-role component (Roundcube is a webmail frontend, not infrastructure-defining software) or a default-vulnerable condition affecting the full installed base. The virtuser_query plugin must be explicitly opted into, which caps the blast radius. The attack complexity is genuinely higher than a typical pre-auth SQLi because the backslash bypass requires database-backend-specific payload tuning. No evidence of automated worm-like propagation.
Why not lower?
Active exploitation is confirmed by a national CERT. A public PoC exists with batch-scanning capability. The vulnerability is pre-auth and network-accessible with full CIA impact on the database. UNK_MassTraction demonstrates that nation-state actors are actively targeting Roundcube infrastructure. Dropping to MEDIUM would understate the urgency for the subset of deployments running virtuser_query — for those instances, the risk is immediate and severe.
Crowdsourced verification payload.
Run this script on each Roundcube server as a user with read access to the Roundcube installation directory (typically www-data or root). Invoke with the Roundcube install path as argument: sudo bash check_cve_2026_48842.sh /var/www/roundcube. No network access or database credentials required.
#!/usr/bin/env bash
# check_cve_2026_48842.sh — Detect CVE-2026-48842 (Roundcube virtuser_query pre-auth SQLi)
# Usage: bash check_cve_2026_48842.sh /path/to/roundcube
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
RC_DIR="${1:-/var/www/roundcube}"
STATUS="UNKNOWN"
# Locate version file
VERSION_FILE=""
for candidate in "$RC_DIR/program/include/iniset.php" "$RC_DIR/index.php" "$RC_DIR/program/lib/Roundcube/rcube.php"; do
if [[ -f "$candidate" ]]; then
VERSION_FILE="$candidate"
break
fi
done
if [[ -z "$VERSION_FILE" ]]; then
echo "[!] Cannot find Roundcube installation at $RC_DIR"
echo "UNKNOWN"
exit 2
fi
# Extract version
VERSION=$(grep -oP "define\\('RCMAIL_VERSION',\\s*'\\K[^']+" "$RC_DIR/program/include/iniset.php" 2>/dev/null || \
grep -oP "'version'\\s*=>\\s*'\\K[^']+" "$RC_DIR/composer.json" 2>/dev/null || \
echo "")
if [[ -z "$VERSION" ]]; then
echo "[!] Could not determine Roundcube version"
echo "UNKNOWN"
exit 2
fi
echo "[*] Roundcube version: $VERSION"
# Check if virtuser_query plugin is enabled
CONFIG_FILE="$RC_DIR/config/config.inc.php"
PLUGIN_ENABLED=false
if [[ -f "$CONFIG_FILE" ]]; then
if grep -qP "['\"]virtuser_query['\"]" "$CONFIG_FILE" 2>/dev/null; then
PLUGIN_ENABLED=true
echo "[!] virtuser_query plugin is ENABLED in config"
else
echo "[*] virtuser_query plugin is NOT enabled in config"
fi
else
echo "[!] Config file not found at $CONFIG_FILE"
fi
# Check if the vulnerable preg_replace pattern exists in the plugin
PLUGIN_FILE="$RC_DIR/plugins/virtuser_query/virtuser_query.php"
USES_PREG=false
if [[ -f "$PLUGIN_FILE" ]]; then
if grep -q 'preg_replace' "$PLUGIN_FILE" 2>/dev/null; then
USES_PREG=true
echo "[!] Plugin code uses vulnerable preg_replace() pattern"
else
echo "[*] Plugin code uses safe str_replace() pattern (patched)"
fi
else
echo "[*] virtuser_query plugin file not found (plugin not installed)"
echo "PATCHED"
exit 0
fi
# Version comparison
version_gte() {
printf '%s\n%s' "$2" "$1" | sort -V -C
}
# Determine vulnerability status
if [[ "$VERSION" == 1.6.* ]]; then
if version_gte "$VERSION" "1.6.16"; then
STATUS="PATCHED"
else
STATUS="VULNERABLE"
fi
elif [[ "$VERSION" == 1.7.* ]]; then
if version_gte "$VERSION" "1.7.1"; then
STATUS="PATCHED"
else
STATUS="VULNERABLE"
fi
elif [[ "$VERSION" == 1.5.* ]] || [[ "$VERSION" == 1.4.* ]]; then
STATUS="VULNERABLE"
echo "[!] EOL version detected — likely vulnerable if plugin is present"
else
# 1.8+ or unknown
STATUS="UNKNOWN"
fi
# Override: if plugin uses str_replace (patched code), mark patched regardless
if [[ "$USES_PREG" == false ]]; then
STATUS="PATCHED"
fi
# Final output
if [[ "$STATUS" == "VULNERABLE" ]]; then
if [[ "$PLUGIN_ENABLED" == true ]]; then
echo "[!!!] VULNERABLE — version $VERSION with virtuser_query ENABLED and unpatched preg_replace()"
echo "VULNERABLE"
exit 1
else
echo "[!] VULNERABLE (code) but plugin not enabled — exploitability requires config change"
echo "VULNERABLE"
exit 1
fi
elif [[ "$STATUS" == "PATCHED" ]]; then
echo "[*] PATCHED — version $VERSION or plugin code uses safe str_replace()"
echo "PATCHED"
exit 0
else
echo "[?] UNKNOWN — could not conclusively determine status"
echo "UNKNOWN"
exit 2
fi