VYPR
Medium severity5.3NVD Advisory· Published Apr 3, 2026· Updated Apr 7, 2026

CVE-2026-35543

CVE-2026-35543

Description

An issue was discovered in Roundcube Webmail before 1.5.14 and 1.6.14. The remote image blocking feature can be bypassed via SVG content (with animate attributes) in an e-mail message. This may lead to information disclosure or access-control bypass.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
roundcube/roundcubemailPackagist
>= 1.7-beta, < 1.7-rc51.7-rc5

Affected products

1

Patches

3
1a63e01542bf

Fix remote image blocking bypass via various SVG animate attributes

https://github.com/roundcube/roundcubemailAleksander MachniakMar 17, 2026via ghsa
3 files changed · +44 9
  • CHANGELOG.md+1 0 modified
    @@ -5,6 +5,7 @@
     - Security: Fix pre-auth arbitrary file write via unsafe deserialization in redis/memcache session handler
     - Security: Fix bug where a password could get changed without providing the old password
     - Security: Fix IMAP Injection + CSRF bypass in mail search
    +- Security: Fix remote image blocking bypass via various SVG animate attributes
     
     ## Release 1.5.13
     
    
  • program/lib/Roundcube/rcube_washtml.php+29 9 modified
    @@ -501,22 +501,22 @@ private function is_funciri_attribute($tag, $attr)
          * Do it in case-insensitive manner.
          *
          * @param DOMElement $node       The element
    -     * @param string     $attr_name  The attribute name
    -     * @param string     $attr_value The attribute value to find
    +     * @param string     $attr_value The attribute value to find (regexp)
          *
          * @return bool True if the specified attribute exists and has the expected value
          */
         private static function attribute_value($node, $attr_name, $attr_value)
         {
             $attr_name = strtolower($attr_name);
    -        $attr_value = strtolower($attr_value);
     
             foreach ($node->attributes as $name => $attr) {
                 if (strtolower($name) === $attr_name) {
    +                $val = trim($attr->nodeValue);
                     // Read the attribute name, remove the namespace (e.g. xlink:href => href)
    -                $val = strtolower(trim($attr->nodeValue));
    -                $val = trim(preg_replace('/^.*:/', '', $val));
    -                if ($attr_value === $val) {
    +                if ($attr_name === 'attributename') {
    +                    $val = trim(preg_replace('/^.*:/', '', $val));
    +                }
    +                if (preg_match($attr_value, $val)) {
                         return true;
                     }
                 }
    @@ -525,6 +525,27 @@ private static function attribute_value($node, $attr_name, $attr_value)
             return false;
         }
     
    +    /**
    +     * Check if the node is an insecure element
    +     *
    +     * @param \DOMElement $node
    +     */
    +    private static function is_insecure_tag($node)
    +    {
    +        $tagName = strtolower($node->nodeName);
    +
    +        if (!in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])) {
    +            return false;
    +        }
    +
    +        if (self::attribute_value($node, 'attributeName', '/^href$/i')) {
    +            return true;
    +        }
    +
    +        return self::attribute_value($node, 'attributeName', '/^(mask|cursor)$/i')
    +            && self::attribute_value($node, 'values', '/url\(/i');
    +    }
    +
         /**
          * The main loop that recurse on a node tree.
          * It output only allowed tags with allowed attributes and allowed inline styles
    @@ -576,10 +597,9 @@ private function dumpHtml($node, $level = 20)
     
                         $node->setAttribute('href', (string) $uri);
                     }
    -                else if (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
    -                    && self::attribute_value($node, 'attributename', 'href')
    -                ) {
    +                else if (self::is_insecure_tag($node)) {
                         // Insecure svg tags
    +                    // TODO: We really should use wash_attribs()/wash_uri() for these cases
                         if ($this->config['add_comments']) {
                             $dump .= "<!-- {$tagName} blocked -->";
                         }
    
  • tests/Framework/Washtml.php+14 0 modified
    @@ -480,6 +480,20 @@ function data_wash_svg_tests()
                     '<html><svg><defs><filter><feImage xlink:href="http://external.site"/></filter></defs></html>',
                     '<svg xmlns="http://www.w3.org/1999/xhtml"><defs><filter><feImage x-washed="xlink:href"></feImage></filter></defs></svg>',
                 ],
    +            [
    +                '<svg><animate attributeName="mask" values="url(https://external.site)" fill="freeze" dur="0.1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="mask" values="none;url(https://external.site);url(https://external.site)"'
    +                    . ' repeatCount="indefinite" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="cursor" attributeType="CSS" values="url(https://external.site),auto"'
    +                    . ' feel="freeze" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
             ];
         }
     
    
39471343ee08

Fix remote image blocking bypass via various SVG animate attributes

https://github.com/roundcube/roundcubemailAleksander MachniakMar 17, 2026via ghsa
3 files changed · +44 9
  • CHANGELOG.md+1 0 modified
    @@ -6,6 +6,7 @@
     - Security: Fix pre-auth arbitrary file write via unsafe deserialization in redis/memcache session handler
     - Security: Fix bug where a password could get changed without providing the old password
     - Security: Fix IMAP Injection + CSRF bypass in mail search
    +- Security: Fix remote image blocking bypass via various SVG animate attributes
     
     ## Release 1.6.13
     
    
  • program/lib/Roundcube/rcube_washtml.php+29 9 modified
    @@ -504,22 +504,22 @@ private function is_funciri_attribute($tag, $attr)
          * Do it in case-insensitive manner.
          *
          * @param DOMElement $node       The element
    -     * @param string     $attr_name  The attribute name
    -     * @param string     $attr_value The attribute value to find
    +     * @param string     $attr_value The attribute value to find (regexp)
          *
          * @return bool True if the specified attribute exists and has the expected value
          */
         private static function attribute_value($node, $attr_name, $attr_value)
         {
             $attr_name = strtolower($attr_name);
    -        $attr_value = strtolower($attr_value);
     
             foreach ($node->attributes as $name => $attr) {
                 if (strtolower($name) === $attr_name) {
    +                $val = trim($attr->nodeValue);
                     // Read the attribute name, remove the namespace (e.g. xlink:href => href)
    -                $val = strtolower(trim($attr->nodeValue));
    -                $val = trim(preg_replace('/^.*:/', '', $val));
    -                if ($attr_value === $val) {
    +                if ($attr_name === 'attributename') {
    +                    $val = trim(preg_replace('/^.*:/', '', $val));
    +                }
    +                if (preg_match($attr_value, $val)) {
                         return true;
                     }
                 }
    @@ -528,6 +528,27 @@ private static function attribute_value($node, $attr_name, $attr_value)
             return false;
         }
     
    +    /**
    +     * Check if the node is an insecure element
    +     *
    +     * @param \DOMElement $node
    +     */
    +    private static function is_insecure_tag($node)
    +    {
    +        $tagName = strtolower($node->nodeName);
    +
    +        if (!in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])) {
    +            return false;
    +        }
    +
    +        if (self::attribute_value($node, 'attributeName', '/^href$/i')) {
    +            return true;
    +        }
    +
    +        return self::attribute_value($node, 'attributeName', '/^(mask|cursor)$/i')
    +            && self::attribute_value($node, 'values', '/url\(/i');
    +    }
    +
         /**
          * The main loop that recurse on a node tree.
          * It output only allowed tags with allowed attributes and allowed inline styles
    @@ -579,10 +600,9 @@ private function dumpHtml($node, $level = 20)
     
                         $node->setAttribute('href', (string) $uri);
                     }
    -                else if (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
    -                    && self::attribute_value($node, 'attributename', 'href')
    -                ) {
    +                else if (self::is_insecure_tag($node)) {
                         // Insecure svg tags
    +                    // TODO: We really should use wash_attribs()/wash_uri() for these cases
                         if ($this->config['add_comments']) {
                             $dump .= "<!-- {$tagName} blocked -->";
                         }
    
  • tests/Framework/Washtml.php+14 0 modified
    @@ -500,6 +500,20 @@ function data_wash_svg_tests()
                     '<html><svg><defs><filter><feImage xlink:href="http://external.site"/></filter></defs></html>',
                     '<svg><defs><filter><feImage x-washed="xlink:href"></feImage></filter></defs></svg>',
                 ],
    +            [
    +                '<svg><animate attributeName="mask" values="url(https://external.site)" fill="freeze" dur="0.1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="mask" values="none;url(https://external.site);url(https://external.site)"'
    +                    . ' repeatCount="indefinite" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="cursor" attributeType="CSS" values="url(https://external.site),auto"'
    +                    . ' feel="freeze" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
             ];
         }
     
    
82ab5eca7b33

Fix remote image blocking bypass via various SVG animate attributes

https://github.com/roundcube/roundcubemailAleksander MachniakMar 17, 2026via ghsa
3 files changed · +44 8
  • CHANGELOG.md+1 0 modified
    @@ -12,6 +12,7 @@ This file includes only changes we consider noteworthy for users, admins and plu
     - Security: Fix pre-auth arbitrary file write via unsafe deserialization in redis/memcache session handler
     - Security: Fix bug where a password could get changed without providing the old password
     - Security: Fix IMAP Injection + CSRF bypass in mail search
    +- Security: Fix remote image blocking bypass via various SVG animate attributes
     
     ## 1.7-rc4
     
    
  • program/lib/Roundcube/rcube_washtml.php+29 8 modified
    @@ -498,21 +498,22 @@ private function is_funciri_attribute($tag, $attr)
          *
          * @param \DOMElement $node       The element
          * @param string      $attr_name  The attribute name
    -     * @param string      $attr_value The attribute value to find
    +     * @param string      $attr_value The attribute value to find (regexp)
          *
          * @return bool True if the specified attribute exists and has the expected value
          */
         private static function attribute_value($node, $attr_name, $attr_value)
         {
             $attr_name = strtolower($attr_name);
    -        $attr_value = strtolower($attr_value);
     
             foreach ($node->attributes as $name => $attr) {
                 if (strtolower($name) === $attr_name) {
    +                $val = trim($attr->nodeValue);
                     // Read the attribute name, remove the namespace (e.g. xlink:href => href)
    -                $val = strtolower(trim($attr->nodeValue));
    -                $val = trim(preg_replace('/^.*:/', '', $val));
    -                if ($attr_value === $val) {
    +                if ($attr_name === 'attributename') {
    +                    $val = trim(preg_replace('/^.*:/', '', $val));
    +                }
    +                if (preg_match($attr_value, $val)) {
                         return true;
                     }
                 }
    @@ -521,6 +522,27 @@ private static function attribute_value($node, $attr_name, $attr_value)
             return false;
         }
     
    +    /**
    +     * Check if the node is an insecure element
    +     *
    +     * @param \DOMElement $node
    +     */
    +    private static function is_insecure_tag($node)
    +    {
    +        $tagName = strtolower($node->nodeName);
    +
    +        if (!in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])) {
    +            return false;
    +        }
    +
    +        if (self::attribute_value($node, 'attributeName', '/^href$/i')) {
    +            return true;
    +        }
    +
    +        return self::attribute_value($node, 'attributeName', '/^(mask|cursor)$/i')
    +            && self::attribute_value($node, 'values', '/url\(/i');
    +    }
    +
         /**
          * The main loop that recurse on a node tree.
          * It output only allowed tags with allowed attributes and allowed inline styles
    @@ -570,10 +592,9 @@ private function dumpHtml($node, $level = 20)
                             }
     
                             $node->setAttribute('href', (string) $uri);
    -                    } elseif (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
    -                        && self::attribute_value($node, 'attributename', 'href')
    -                    ) {
    +                    } elseif (self::is_insecure_tag($node)) {
                             // Insecure svg tags
    +                        // TODO: We really should use wash_attribs()/wash_uri() for these cases
                             if ($this->config['add_comments']) {
                                 $dump .= "<!-- {$tagName} blocked -->";
                             }
    
  • tests/Framework/WashtmlTest.php+14 0 modified
    @@ -517,6 +517,20 @@ public static function provide_wash_svg_tests_cases(): iterable
                     '<html><svg><defs><filter><feImage xlink:href="http://external.site"/></filter></defs></html>',
                     '<svg><defs><filter><feimage x-washed="xlink:href"></feimage></filter></defs></svg>',
                 ],
    +            [
    +                '<svg><animate attributeName="mask" values="url(https://external.site)" fill="freeze" dur="0.1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="mask" values="none;url(https://external.site);url(https://external.site)"'
    +                    . ' repeatCount="indefinite" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
    +            [
    +                '<svg><animate attributeName="cursor" attributeType="CSS" values="url(https://external.site),auto"'
    +                    . ' feel="freeze" dur="1s" /></svg>',
    +                '<svg><!-- animate blocked --></svg>',
    +            ],
             ];
         }
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

1