Summary
The folder-size dialog builds its HTML by interpolating the IMAP folder name and folder id directly into an HTML string without escaping, then inserts it via rcmail.simple_dialog(). A folder whose name (or path) contains HTML results in DOM-based XSS in the victim's authenticated Roundcube session.
- Type: DOM-based XSS (CWE-79)
- Reviewed at commit:
52b6ed6 (current master)
- Severity: Medium — script execution in the webmail origin. Exploitability depends on an attacker being able to place a folder with a crafted name into the victim's view (e.g. a shared/public IMAP namespace the victim is subscribed to); self-inflicted otherwise.
Root cause
In assets/main.js the per-folder row is assembled with template literals and no escaping:
html += `
<tr>
<td class="name ${mailbox.virtual ? 'virtual' : ''}"
onclick="return ${mailbox.virtual ? 0 : 1} ? rcmail.command('list', '${id}', this, event) : ''"
title="${id}">
<div style="margin-left: ${level * 1.5}em">${mailbox.name}</div>
</td>
...
The resulting string is handed to rcmail.simple_dialog(generatePopupContent(resp), ...), which inserts it as HTML.
mailbox.name and id come from rcmail.env.mailboxes, which Roundcube core populates with the raw (unescaped) folder name/path — core escapes it with rcube::Q() only at its own render sites, and expects client code to escape before injecting into HTML. Here ${mailbox.name} (HTML text context), ${id} in title="…" (attribute context) and ${id} inside the onclick="…rcmail.command('list', '${id}', …)" (JS-string-in-attribute context) are all unescaped, so a folder name/path containing e.g. <img src=x onerror=…> or a '/" breaks out.
Proof of concept
On a server where the attacker can create a folder visible to the victim (shared namespace), create a folder named:
<img src=x onerror=alert(document.domain)>
When the victim opens Folder options → Show folder size, the payload executes. (A folder id/path containing a single quote similarly breaks out of the onclick handler.)
Recommended fix
HTML-escape all untrusted values before interpolation, per context: escape mailbox.name for HTML text, and id for both the title attribute and the JS-string in onclick. Prefer building the row with DOM APIs (document.createElement + textContent + dataset) and attaching the click handler in JS, or run values through an escaping helper (e.g. a small escapeHtml() and JSON.stringify for the JS-string).
Disclosure
The repository has no SECURITY.md and GitHub private vulnerability reporting is not enabled, so there is no private channel; reporting here so it can be fixed. Happy to open a PR. Found during a security review of Roundcube and commonly-used plugins.
Summary
The folder-size dialog builds its HTML by interpolating the IMAP folder name and folder id directly into an HTML string without escaping, then inserts it via
rcmail.simple_dialog(). A folder whose name (or path) contains HTML results in DOM-based XSS in the victim's authenticated Roundcube session.52b6ed6(currentmaster)Root cause
In
assets/main.jsthe per-folder row is assembled with template literals and no escaping:The resulting string is handed to
rcmail.simple_dialog(generatePopupContent(resp), ...), which inserts it as HTML.mailbox.nameandidcome fromrcmail.env.mailboxes, which Roundcube core populates with the raw (unescaped) folder name/path — core escapes it withrcube::Q()only at its own render sites, and expects client code to escape before injecting into HTML. Here${mailbox.name}(HTML text context),${id}intitle="…"(attribute context) and${id}inside theonclick="…rcmail.command('list', '${id}', …)"(JS-string-in-attribute context) are all unescaped, so a folder name/path containing e.g.<img src=x onerror=…>or a'/"breaks out.Proof of concept
On a server where the attacker can create a folder visible to the victim (shared namespace), create a folder named:
When the victim opens Folder options → Show folder size, the payload executes. (A folder id/path containing a single quote similarly breaks out of the
onclickhandler.)Recommended fix
HTML-escape all untrusted values before interpolation, per context: escape
mailbox.namefor HTML text, andidfor both thetitleattribute and the JS-string inonclick. Prefer building the row with DOM APIs (document.createElement+textContent+dataset) and attaching the click handler in JS, or run values through an escaping helper (e.g. a smallescapeHtml()andJSON.stringifyfor the JS-string).Disclosure
The repository has no
SECURITY.mdand GitHub private vulnerability reporting is not enabled, so there is no private channel; reporting here so it can be fixed. Happy to open a PR. Found during a security review of Roundcube and commonly-used plugins.