{"id":"GHSA-3mr9-p497-58f6","summary":"Contao crawler leaks auth credentials to external hosts","details":"### Summary\nContao's crawler tries to prevent confidential HTTP client options from being sent to external domains by creating a scoped client: full options for root page origins, cleaned options for everything else. The cleaner removes `Cookie` and `Authorization` headers, but it removes the non-Symfony option names `basic_auth` and `bearer_auth` instead of Symfony HttpClient's real `auth_basic` and `auth_bearer` options.\n\nWhen `contao.crawl.default_http_client_options` contains Basic or Bearer authentication for a protected staging/production site, those credentials remain in the \"clean\" client used for external links or configured additional URIs. An attacker who can get an external URL crawled, for example through a link on a crawled page while the broken-link checker is enabled, can receive the crawler credentials.\n\n## Technical Detail\n\n### Root Cause\n\n```php\n// core-bundle/src/Crawl/Escargot/Factory.php:175-209 @ e550b92a01ef625bd546e6c3956dd200af05ebf0\nprivate function createHttpClient(array $options = []): HttpClientInterface\n{\n    $options = array_merge_recursive(\n        [\n            'headers' =\u003e [\n                'accept' =\u003e 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n                'user-agent' =\u003e self::USER_AGENT,\n            ],\n            'max_duration' =\u003e 10,\n        ],\n        array_merge_recursive($this-\u003egetDefaultHttpClientOptions(), $options),\n    );\n\n    $cleanOptions = $this-\u003ecleanOptionsFromConfidentialData($options);\n\n    if ($options === $cleanOptions) {\n        return ($this-\u003ehttpClientFactory)($options);\n    }\n\n    $scopedOptionsByRegex = [];\n\n    foreach ($this-\u003egetRootPageUriCollection()-\u003eall() as $rootPageUri) {\n        $scopedOptionsByRegex[preg_quote($this-\u003egetOriginFromUri($rootPageUri))] = $options;\n    }\n\n    return new ScopingHttpClient(($this-\u003ehttpClientFactory)($cleanOptions), $scopedOptionsByRegex);\n}\n```\n\n```php\n// core-bundle/src/Crawl/Escargot/Factory.php:226-247 @ e550b92a01ef625bd546e6c3956dd200af05ebf0\nforeach ($options as $k =\u003e $v) {\n    if ('headers' === $k) {\n        foreach ($v as $header =\u003e $value) {\n            if (\\in_array(strtolower($header), ['authorization', 'cookie'], true)) {\n                continue;\n            }\n\n            $cleanOptions['headers'][$header] = $value;\n        }\n\n        continue;\n    }\n\n    if ('basic_auth' === $k || 'bearer_auth' === $k) {\n        continue;\n    }\n\n    $cleanOptions[$k] = $v;\n}\n```\n\nSymfony HttpClient authentication options are `auth_basic` and `auth_bearer`; Contao's own manual documents `auth_basic` for crawler Basic Authentication. Because the cleaner only strips `basic_auth` and `bearer_auth`, the \"clean\" default client for non-root-page hosts still carries the real auth options. The existing factory test intends to assert that `Authorization` is not sent to `www.foreign-domain.com`, but its mock client factory ignores the `$defaultOptions` argument, so it does not catch auth options that survive into `HttpClient::create($cleanOptions)`.\n\n\n## Suggested Mitigation\n\nStrip the actual Symfony HttpClient authentication option keys from the clean client. Include NTLM as a defensive extension because Symfony documents it as another auth option.\n\n```diff\n-            if ('basic_auth' === $k || 'bearer_auth' === $k) {\n+            if (\\in_array($k, ['auth_basic', 'auth_bearer', 'auth_ntlm', 'basic_auth', 'bearer_auth'], true)) {\n                 continue;\n             }\n```\n\nAlso update the factory test so the mock factory records or preserves `$defaultOptions`; otherwise the test does not verify what `HttpClient::create($cleanOptions)` receives in production.\n\n## Impact\n\n- **Direct primitive**: disclosure of crawler Basic/Bearer credentials to an external host reached by the crawler.\n- **Chain potential**: if those credentials protect a staging or pre-publication environment, an attacker can use them to access that environment. The impact depends on what the leaked credential unlocks.\n- **Realistic exploitation**: a content editor adds a link to `https://attacker.example/probe` on a page that the crawler visits. When an administrator or scheduled maintenance run starts the broken-link checker with crawler Basic/Bearer authentication configured, the request to the attacker URL includes the generated `Authorization` header.","aliases":["CVE-2026-55824"],"modified":"2026-08-06T19:45:16.256360775Z","published":"2026-08-06T19:43:02Z","database_specific":{"cwe_ids":["CWE-200"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-08-06T19:43:02Z","nvd_published_at":"2026-07-31T19:17:11Z"},"references":[{"type":"WEB","url":"https://github.com/contao/contao/security/advisories/GHSA-3mr9-p497-58f6"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55824"},{"type":"WEB","url":"https://github.com/contao/contao/commit/5bc6e3f900c439313df57aa561d0865792aafa05"},{"type":"WEB","url":"https://github.com/contao/contao/commit/80425d28cdf66280a209bd3f5bc31b1a76901a04"},{"type":"WEB","url":"https://contao.org/en/security-advisories/credentials-disclosure-in-the-crawler"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/contao/contao/CVE-2026-55824.yaml"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/contao/core-bundle/CVE-2026-55824.yaml"},{"type":"PACKAGE","url":"https://github.com/contao/contao"}],"affected":[{"package":{"name":"contao/contao","ecosystem":"Packagist","purl":"pkg:composer/contao/contao"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.13.0"},{"fixed":"5.3.47"}]}],"versions":["4.13.0","4.13.1","4.13.10","4.13.11","4.13.12","4.13.13","4.13.14","4.13.15","4.13.16","4.13.17","4.13.18","4.13.19","4.13.2","4.13.20","4.13.21","4.13.22","4.13.23","4.13.24","4.13.25","4.13.26","4.13.27","4.13.28","4.13.29","4.13.3","4.13.30","4.13.31","4.13.32","4.13.33","4.13.34","4.13.35","4.13.36","4.13.37","4.13.38","4.13.39","4.13.4","4.13.40","4.13.41","4.13.42","4.13.43","4.13.44","4.13.45","4.13.46","4.13.47","4.13.48","4.13.49","4.13.5","4.13.50","4.13.51","4.13.52","4.13.53","4.13.54","4.13.55","4.13.56","4.13.57","4.13.58","4.13.6","4.13.7","4.13.8","4.13.9","5.0.0","5.0.0-RC1","5.0.0-RC2","5.0.0-RC3","5.0.0-RC4","5.0.1","5.0.10","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.0.7","5.0.8","5.0.9","5.1.0","5.1.0-RC1","5.1.0-RC2","5.1.0-RC3","5.1.1","5.1.10","5.1.11","5.1.2","5.1.3","5.1.4","5.1.5","5.1.6","5.1.7","5.1.8","5.1.9","5.2.0","5.2.0-RC1","5.2.0-RC2","5.2.0-RC3","5.2.0-RC4","5.2.0-RC5","5.2.0-RC6","5.2.1","5.2.10","5.2.2","5.2.3","5.2.4","5.2.5","5.2.6","5.2.7","5.2.8","5.2.9","5.3.0","5.3.0-RC1","5.3.0-RC2","5.3.0-RC3","5.3.0-RC4","5.3.1","5.3.10","5.3.11","5.3.12","5.3.13","5.3.14","5.3.15","5.3.16","5.3.17","5.3.18","5.3.19","5.3.2","5.3.20","5.3.21","5.3.22","5.3.23","5.3.24","5.3.25","5.3.26","5.3.27","5.3.28","5.3.29","5.3.3","5.3.30","5.3.31","5.3.32","5.3.33","5.3.34","5.3.35","5.3.36","5.3.37","5.3.38","5.3.39","5.3.4","5.3.40","5.3.41","5.3.42","5.3.43","5.3.44","5.3.45","5.3.46","5.3.5","5.3.6","5.3.7","5.3.8","5.3.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3mr9-p497-58f6/GHSA-3mr9-p497-58f6.json"}},{"package":{"name":"contao/contao","ecosystem":"Packagist","purl":"pkg:composer/contao/contao"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.4.0"},{"fixed":"5.7.7"}]}],"versions":["5.4.0","5.4.1","5.4.10","5.4.11","5.4.12","5.4.13","5.4.14","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.4.9","5.5.0","5.5.0-RC1","5.5.0-RC2","5.5.0-RC3","5.5.0-RC4","5.5.1","5.5.10","5.5.11","5.5.12","5.5.13","5.5.14","5.5.15","5.5.16","5.5.2","5.5.3","5.5.4","5.5.5","5.5.6","5.5.7","5.5.8","5.5.9","5.6.0","5.6.0-RC1","5.6.0-RC2","5.6.0-RC3","5.6.1","5.6.10","5.6.11","5.6.2","5.6.3","5.6.4","5.6.5","5.6.6","5.6.7","5.6.8","5.6.9","5.7.0","5.7.0-RC1","5.7.0-RC2","5.7.0-RC3","5.7.0-RC4","5.7.1","5.7.2","5.7.3","5.7.4","5.7.5","5.7.6"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3mr9-p497-58f6/GHSA-3mr9-p497-58f6.json"}},{"package":{"name":"contao/core-bundle","ecosystem":"Packagist","purl":"pkg:composer/contao/core-bundle"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.13.0"},{"fixed":"5.3.47"}]}],"versions":["4.13.0","4.13.1","4.13.10","4.13.11","4.13.12","4.13.13","4.13.14","4.13.15","4.13.16","4.13.17","4.13.18","4.13.19","4.13.2","4.13.20","4.13.21","4.13.22","4.13.23","4.13.24","4.13.25","4.13.26","4.13.27","4.13.28","4.13.29","4.13.3","4.13.30","4.13.31","4.13.32","4.13.33","4.13.34","4.13.35","4.13.36","4.13.37","4.13.38","4.13.39","4.13.4","4.13.40","4.13.41","4.13.42","4.13.43","4.13.44","4.13.45","4.13.46","4.13.47","4.13.48","4.13.49","4.13.5","4.13.50","4.13.51","4.13.52","4.13.53","4.13.54","4.13.55","4.13.56","4.13.57","4.13.58","4.13.6","4.13.7","4.13.8","4.13.9","5.0.0","5.0.0-RC1","5.0.0-RC2","5.0.0-RC3","5.0.0-RC4","5.0.1","5.0.10","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.0.7","5.0.8","5.0.9","5.1.0","5.1.0-RC1","5.1.0-RC2","5.1.0-RC3","5.1.1","5.1.10","5.1.11","5.1.2","5.1.3","5.1.4","5.1.5","5.1.6","5.1.7","5.1.8","5.1.9","5.2.0","5.2.0-RC1","5.2.0-RC2","5.2.0-RC3","5.2.0-RC4","5.2.0-RC5","5.2.0-RC6","5.2.1","5.2.10","5.2.2","5.2.3","5.2.4","5.2.5","5.2.6","5.2.7","5.2.8","5.2.9","5.3.0","5.3.0-RC1","5.3.0-RC2","5.3.0-RC3","5.3.0-RC4","5.3.1","5.3.10","5.3.11","5.3.12","5.3.13","5.3.14","5.3.15","5.3.16","5.3.17","5.3.18","5.3.19","5.3.2","5.3.20","5.3.21","5.3.22","5.3.23","5.3.24","5.3.25","5.3.26","5.3.27","5.3.28","5.3.29","5.3.3","5.3.30","5.3.31","5.3.32","5.3.33","5.3.34","5.3.35","5.3.36","5.3.37","5.3.38","5.3.39","5.3.4","5.3.40","5.3.41","5.3.42","5.3.43","5.3.44","5.3.45","5.3.46","5.3.5","5.3.6","5.3.7","5.3.8","5.3.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3mr9-p497-58f6/GHSA-3mr9-p497-58f6.json"}},{"package":{"name":"contao/core-bundle","ecosystem":"Packagist","purl":"pkg:composer/contao/core-bundle"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.4.0"},{"fixed":"5.7.7"}]}],"versions":["5.4.0","5.4.1","5.4.10","5.4.11","5.4.12","5.4.13","5.4.14","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.4.9","5.5.0","5.5.0-RC1","5.5.0-RC2","5.5.0-RC3","5.5.0-RC4","5.5.1","5.5.10","5.5.11","5.5.12","5.5.13","5.5.14","5.5.15","5.5.16","5.5.2","5.5.3","5.5.4","5.5.5","5.5.6","5.5.7","5.5.8","5.5.9","5.6.0","5.6.0-RC1","5.6.0-RC2","5.6.0-RC3","5.6.1","5.6.10","5.6.11","5.6.2","5.6.3","5.6.4","5.6.5","5.6.6","5.6.7","5.6.8","5.6.9","5.7.0","5.7.0-RC1","5.7.0-RC2","5.7.0-RC3","5.7.0-RC4","5.7.1","5.7.2","5.7.3","5.7.4","5.7.5","5.7.6"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3mr9-p497-58f6/GHSA-3mr9-p497-58f6.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:N/A:N"}]}