GHSA-3mr9-p497-58f6 CVE-2026-55824
Contao crawler leaks auth credentials to external hosts
- Severity
- 2.6 / 10
- Low · CVSS 3.1
- Exploit chance (EPSS)
- 0.24%
- Chance of exploitation in the next 30 days. Higher than 15.57% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
- Fix
- Available
- 2 packages have a fixed version
Description
Summary
Contao'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.
When 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.
Technical Detail
Root Cause
// core-bundle/src/Crawl/Escargot/Factory.php:175-209 @ e550b92a01ef625bd546e6c3956dd200af05ebf0
private function createHttpClient(array $options = []): HttpClientInterface
{
$options = array_merge_recursive(
[
'headers' => [
'accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'user-agent' => self::USER_AGENT,
],
'max_duration' => 10,
],
array_merge_recursive($this->getDefaultHttpClientOptions(), $options),
);
$cleanOptions = $this->cleanOptionsFromConfidentialData($options);
if ($options === $cleanOptions) {
return ($this->httpClientFactory)($options);
}
$scopedOptionsByRegex = [];
foreach ($this->getRootPageUriCollection()->all() as $rootPageUri) {
$scopedOptionsByRegex[preg_quote($this->getOriginFromUri($rootPageUri))] = $options;
}
return new ScopingHttpClient(($this->httpClientFactory)($cleanOptions), $scopedOptionsByRegex);
}
// core-bundle/src/Crawl/Escargot/Factory.php:226-247 @ e550b92a01ef625bd546e6c3956dd200af05ebf0
foreach ($options as $k => $v) {
if ('headers' === $k) {
foreach ($v as $header => $value) {
if (\in_array(strtolower($header), ['authorization', 'cookie'], true)) {
continue;
}
$cleanOptions['headers'][$header] = $value;
}
continue;
}
if ('basic_auth' === $k || 'bearer_auth' === $k) {
continue;
}
$cleanOptions[$k] = $v;
}
Symfony 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).
Suggested Mitigation
Strip 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.
- if ('basic_auth' === $k || 'bearer_auth' === $k) {
+ if (\in_array($k, ['auth_basic', 'auth_bearer', 'auth_ntlm', 'basic_auth', 'bearer_auth'], true)) {
continue;
}
Also 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.
Impact
- Direct primitive: disclosure of crawler Basic/Bearer credentials to an external host reached by the crawler.
- 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.
- Realistic exploitation: a content editor adds a link to
https://attacker.example/probeon 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 generatedAuthorizationheader.
Affected versions
Packagist
| Package | Vulnerable | Fixed in |
|---|---|---|
| contao/contao | >= 4.13.0, < 5.3.47 |
5.3.47 |
| contao/contao | >= 5.4.0, < 5.7.7 |
5.7.7 |
| contao/core-bundle | >= 4.13.0, < 5.3.47 |
5.3.47 |
| contao/core-bundle | >= 5.4.0, < 5.7.7 |
5.7.7 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
contao/contao Packagist → 5.3.47
- composer
composer require contao/contao:^5.3.47
contao/core-bundle Packagist → 5.3.47
- composer
composer require contao/core-bundle:^5.3.47
Severity breakdown
CVSS 3.1 2.6
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:N/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- Low
- User interaction
- Required
- Scope
- Unchanged
- Confidentiality
- Low
- Integrity
- None
- Availability
- None
Weakness
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
How to prevent it
- Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
References
Advisories
Packages
Other links
- github.com/contao/contao/security/advisories/GHSA-3mr9-p497-58f6
- github.com/contao/contao/commit/5bc6e3f900c439313df57aa561d0865792aafa05
- github.com/contao/contao/commit/80425d28cdf66280a209bd3f5bc31b1a76901a04
- contao.org/en/security-advisories/credentials-disclosure-in-the-crawler
- github.com/FriendsOfPHP/security-advisories/blob/master/contao/contao/CVE-2026-…
- github.com/FriendsOfPHP/security-advisories/blob/master/contao/core-bundle/CVE-…
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-9ff2-p842-45wq Contao: Cross-site request forgery in custom backend actions | Low | Also affects contao/core-bundle |
| GHSA-q6wp-fr43-gm9v Contao: Improper access control in the preview links module | Medium | Also affects contao/core-bundle |
| GHSA-5974-gfqc-wrcm Contao: Improper access control in the table access voter | Medium | Also affects contao/core-bundle |
| GHSA-mrvp-7wmx-5m4h Contao: Path traversal in the images controller | Medium | Also affects contao/core-bundle |
| GHSA-mfxh-vp55-7gc6 Contao: The registration module re-sends activation mails | Medium | Also affects contao/core-bundle |
| GHSA-x2rp-9qf7-2fmq Contao: Protected page content is disclosed to anonymous visitors after contao.search.index_protected is disabled | Medium | Also affects contao/core-bundle |
| GHSA-grm4-wm43-9jh5 Contao: Possible path traversal in job download URIs | Low | Also affects contao/contao |
| GHSA-87mg-5grr-rhwh Contao: Server-Side Request Forgery (SSRF) via Unvalidated RSS Feed URL in Feed Reader Module | Low | Also affects contao/contao |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor