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

CVE-2026-35540

CVE-2026-35540

Description

An issue was discovered in Roundcube Webmail 1.6.0 before 1.6.14. Insufficient Cascading Style Sheets (CSS) sanitization in HTML e-mail messages may lead to SSRF or Information Disclosure, e.g., if stylesheet links point to local network hosts.

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
  • cpe:2.3:a:roundcube:webmail:*:*:*:*:*:*:*:*
    Range: >=1.6.0,<1.6.14

Patches

2
579b68eff906

Fix SSRF + Information Disclosure via stylesheet links to a local network hosts

https://github.com/roundcube/roundcubemailAleksander MachniakMar 18, 2026via ghsa
7 files changed · +83 3
  • CHANGELOG.md+1 0 modified
    @@ -16,6 +16,7 @@ This file includes only changes we consider noteworthy for users, admins and plu
     - Security: Fix remote image blocking bypass via a crafted body background attribute
     - Security: Fix fixed position mitigation bypass via use of !important
     - Security: Fix XSS issue in a HTML attachment preview
    +- Security: Fix SSRF + Information Disclosure via stylesheet links to a local network hosts
     
     ## 1.7-rc4
     
    
  • composer.json+1 0 modified
    @@ -9,6 +9,7 @@
             "guzzlehttp/promises": "^2.0",
             "league/commonmark": "^2.7",
             "masterminds/html5": "~2.9.0",
    +        "mlocati/ip-lib": "^1.22.0",
             "pear/auth_sasl": "~1.2.0",
             "pear/crypt_gpg": "~1.6.3",
             "pear/mail_mime": "~1.10.11",
    
  • program/actions/mail/index.php+1 1 modified
    @@ -1280,7 +1280,7 @@ public static function washtml_link_callback($tag, $attribs, $content, $washtml)
             if (isset($attrib['href'])) {
                 $attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
     
    -            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
    +            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href']) && !rcube_utils::is_local_url($attrib['href'])) {
                     $tempurl = 'tmp-' . md5($attrib['href']) . '.css';
                     $_SESSION['modcssurls'][$tempurl] = $attrib['href'];
                     $attrib['href'] = $rcmail->url([
    
  • program/actions/utils/modcss.php+1 1 modified
    @@ -48,7 +48,7 @@ public function run($args = [])
             $ctype = null;
     
             try {
    -            $client = rcube::get_instance()->get_http_client();
    +            $client = rcube::get_instance()->get_http_client(['allow_redirects' => false]);
                 $response = $client->get($realurl);
     
                 $source = $response->getBody();
    
  • program/lib/Roundcube/rcube_utils.php+44 0 modified
    @@ -1,5 +1,7 @@
     <?php
     
    +use IPLib\Factory;
    +
     /*
      +-----------------------------------------------------------------------+
      | This file is part of the Roundcube Webmail client                     |
    @@ -414,6 +416,48 @@ public static function html_identifier($str, $encode = false)
             return asciiwords($str, true, '_');
         }
     
    +    /**
    +     * Check if an URL point to a local network location.
    +     *
    +     * @param string $url
    +     *
    +     * @return bool
    +     */
    +    public static function is_local_url($url)
    +    {
    +        $host = parse_url($url, \PHP_URL_HOST);
    +
    +        if (is_string($host)) {
    +            // TODO: This is pretty fast, but a single message can contain multiple links
    +            // to the same target, maybe we should do some in-memory caching.
    +            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
    +                $nets = [
    +                    '127.0.0.0/8',    // loopback
    +                    '10.0.0.0/8',     // RFC1918
    +                    '172.16.0.0/12',  // RFC1918
    +                    '192.168.0.0/16', // RFC1918
    +                    '169.254.0.0/16', // link-local / cloud metadata
    +                    '::1/128',
    +                    'fc00::/7',
    +                ];
    +
    +                foreach ($nets as $net) {
    +                    $range = Factory::parseRangeString($net);
    +                    if ($range->contains($address)) {
    +                        return true;
    +                    }
    +                }
    +
    +                return false;
    +            }
    +
    +            // FIXME: Should we accept any non-fqdn hostnames?
    +            return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host);
    +        }
    +
    +        return false;
    +    }
    +
         /**
          * Replace all css definitions with #container [def]
          * and remove css-inlined scripting, make position style safe
    
  • program/lib/Roundcube/rcube_washtml.php+1 1 modified
    @@ -384,7 +384,7 @@ private function wash_uri($uri, $blocked_source = false, $is_image = true)
             }
     
             if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
    -            if (!empty($this->config['allow_remote'])) {
    +            if (!empty($this->config['allow_remote']) || rcube_utils::is_local_url($uri)) {
                     return $uri;
                 }
     
    
  • tests/Framework/UtilsTest.php+34 0 modified
    @@ -639,6 +639,40 @@ public function test_file2class()
             }
         }
     
    +    /**
    +     * Test is_local_url()
    +     *
    +     * @dataProvider provide_is_local_url_cases
    +     */
    +    #[DataProvider('provide_is_local_url_cases')]
    +    public function test_is_local_url($input, $output)
    +    {
    +        $this->assertSame($output, \rcube_utils::is_local_url($input));
    +    }
    +
    +    /**
    +     * Test-Cases for is_local_url() test
    +     */
    +    public static function provide_is_local_url_cases(): iterable
    +    {
    +        return [
    +            // Local hosts
    +            ['https://127.0.0.1', true],
    +            ['https://10.1.1.1', true],
    +            ['https://172.16.0.1', true],
    +            ['https://192.168.0.100', true],
    +            ['https://169.254.0.200', true],
    +            ['http://[fc00::1]', true],
    +            ['ftp://[::1]:8080', true],
    +            ['//127.0.0.1', true],
    +            ['http://localhost', true],
    +            ['http://localhost.localdomain', true],
    +            // Non-local hosts
    +            ['http://[2001:470::76:0:0:0:2]', false],
    +            ['http://domain.tld', false],
    +        ];
    +    }
    +
         /**
          * rcube:utils::strtotime()
          */
    
27ec6cc9cb25

Fix SSRF + Information Disclosure via stylesheet links to a local network hosts

https://github.com/roundcube/roundcubemailAleksander MachniakMar 18, 2026via ghsa
7 files changed · +85 5
  • CHANGELOG.md+1 0 modified
    @@ -10,6 +10,7 @@
     - Security: Fix remote image blocking bypass via a crafted body background attribute
     - Security: Fix fixed position mitigation bypass via use of !important
     - Security: Fix XSS issue in a HTML attachment preview
    +- Security: Fix SSRF + Information Disclosure via stylesheet links to a local network hosts
     
     ## Release 1.6.13
     
    
  • composer.json-dist+2 1 modified
    @@ -20,7 +20,8 @@
             "roundcube/rtf-html-php": "~2.1",
             "masterminds/html5": "~2.7.0",
             "bacon/bacon-qr-code": "^2.0.0",
    -        "guzzlehttp/guzzle": "^7.3.0"
    +        "guzzlehttp/guzzle": "^7.3.0",
    +        "mlocati/ip-lib": "^1.22.0"
         },
         "require-dev": {
             "phpunit/phpunit": "^9"
    
  • program/actions/mail/index.php+1 1 modified
    @@ -1274,7 +1274,7 @@ public static function washtml_link_callback($tag, $attribs, $content, $washtml)
             if (isset($attrib['href'])) {
                 $attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
     
    -            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
    +            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href']) && !rcube_utils::is_local_url($attrib['href'])) {
                     $tempurl = 'tmp-' . md5($attrib['href']) . '.css';
                     $_SESSION['modcssurls'][$tempurl] = $attrib['href'];
                     $attrib['href'] = $rcmail->url([
    
  • program/actions/utils/modcss.php+1 1 modified
    @@ -47,7 +47,7 @@ public function run($args = [])
             $ctype  = null;
     
             try {
    -            $client   = rcube::get_instance()->get_http_client();
    +            $client = rcube::get_instance()->get_http_client(['allow_redirects' => false]);
                 $response = $client->get($realurl);
     
                 if (!empty($response)) {
    
  • program/lib/Roundcube/rcube_utils.php+45 1 modified
    @@ -1,6 +1,8 @@
     <?php
     
    -/**
    +use IPLib\Factory;
    +
    +/*
      +-----------------------------------------------------------------------+
      | This file is part of the Roundcube Webmail client                     |
      |                                                                       |
    @@ -419,6 +421,48 @@ public static function html_identifier($str, $encode = false)
             return asciiwords($str, true, '_');
         }
     
    +    /**
    +     * Check if an URL point to a local network location.
    +     *
    +     * @param string $url
    +     *
    +     * @return bool
    +     */
    +    public static function is_local_url($url)
    +    {
    +        $host = parse_url($url, \PHP_URL_HOST);
    +
    +        if (is_string($host)) {
    +            // TODO: This is pretty fast, but a single message can contain multiple links
    +            // to the same target, maybe we should do some in-memory caching.
    +            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
    +                $nets = [
    +                    '127.0.0.0/8',    // loopback
    +                    '10.0.0.0/8',     // RFC1918
    +                    '172.16.0.0/12',  // RFC1918
    +                    '192.168.0.0/16', // RFC1918
    +                    '169.254.0.0/16', // link-local / cloud metadata
    +                    '::1/128',
    +                    'fc00::/7',
    +                ];
    +
    +                foreach ($nets as $net) {
    +                    $range = Factory::parseRangeString($net);
    +                    if ($range->contains($address)) {
    +                        return true;
    +                    }
    +                }
    +
    +                return false;
    +            }
    +
    +            // FIXME: Should we accept any non-fqdn hostnames?
    +            return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host);
    +        }
    +
    +        return false;
    +    }
    +
         /**
          * Replace all css definitions with #container [def]
          * and remove css-inlined scripting, make position style safe
    
  • program/lib/Roundcube/rcube_washtml.php+1 1 modified
    @@ -393,7 +393,7 @@ private function wash_uri($uri, $blocked_source = false, $is_image = true)
             }
     
             if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
    -            if (!empty($this->config['allow_remote'])) {
    +            if (!empty($this->config['allow_remote']) || rcube_utils::is_local_url($uri)) {
                     return $uri;
                 }
     
    
  • tests/Framework/Utils.php+34 0 modified
    @@ -556,6 +556,40 @@ function test_file2class()
             }
         }
     
    +    /**
    +     * Test is_local_url()
    +     *
    +     * @dataProvider provide_is_local_url_cases
    +     */
    +    #[DataProvider('provide_is_local_url_cases')]
    +    public function test_is_local_url($input, $output)
    +    {
    +        $this->assertSame($output, \rcube_utils::is_local_url($input));
    +    }
    +
    +    /**
    +     * Test-Cases for is_local_url() test
    +     */
    +    public static function provide_is_local_url_cases(): iterable
    +    {
    +        return [
    +            // Local hosts
    +            ['https://127.0.0.1', true],
    +            ['https://10.1.1.1', true],
    +            ['https://172.16.0.1', true],
    +            ['https://192.168.0.100', true],
    +            ['https://169.254.0.200', true],
    +            ['http://[fc00::1]', true],
    +            ['ftp://[::1]:8080', true],
    +            ['//127.0.0.1', true],
    +            ['http://localhost', true],
    +            ['http://localhost.localdomain', true],
    +            // Non-local hosts
    +            ['http://[2001:470::76:0:0:0:2]', false],
    +            ['http://domain.tld', false],
    +        ];
    +    }
    +
         /**
          * rcube:utils::strtotime()
          */
    

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

7

News mentions

0

No linked articles in our index yet.