VDB
Sign up
LOW3.1

GHSA-87mg-5grr-rhwh

Contao: Server-Side Request Forgery (SSRF) via Unvalidated RSS Feed URL in Feed Reader Module

Quick fix

GHSA-87mg-5grr-rhwh — contao/contao: upgrade to the fixed version with the command below.

composer require contao/contao:^5.3.48

Details

### Summary

The Feed Reader front-end module passes RSS feed URLs from its configuration directly to `$this->feedIo->read($url)` without any scheme validation or private-IP blocklist. A backend user with module-edit permissions can configure an arbitrary URL pointing to internal network services, cloud-provider metadata endpoints, or loopback addresses, causing the server to fetch those resources unconditionally. Confirmed live: the server successfully reaches the internal MySQL database container and its own loopback Apache instance.

---

### Details

In `core-bundle/src/Controller/FrontendModule/FeedReaderController.php`, the `getResponse()` function iterates over the configured feed URLs and passes each one directly to the HTTP client with no validation:

```php // Line 50-55 foreach (StringUtil::trimsplit('[\n\t ]', trim($model->rss_feed)) as $url) { try { $feed = $this->cache->get( 'feed_reader_'.$model->id.'_'.md5($url), function (ItemInterface $item) use ($url, $model) { $readerResult = $this->feedIo->read($url, new Feed()); // <-- no validation ```

The DCA field definition for `rss_feed` in `tl_module.php` carries no URL scheme or host validation: ```php 'eval' => array('mandatory'=>true, 'decodeEntities'=>true, 'style'=>'height:60px') ```

The HTTP client is wired as `@psr18.http_client` (Symfony HttpClient) with no SSRF protection configured (`NoPrivateNetworkHttpClient` is not used).

---

### Impact

This is a CWE-918 (SSRF) vulnerability. A backend user with module-edit access can:

1. **Enumerate internal network services** -- probe any IP/port on the internal network by observing response times and error messages 2. **Reach internal APIs** -- access unauthenticated services inside the Docker/Kubernetes network (databases, caches, admin panels) 3. **Steal cloud metadata credentials** -- on AWS, fetch `http://169.254.169.254/latest/meta-data/iam/security-credentials/` to obtain IAM role credentials (IMDSv1 has no authentication) 4. **Pivot to internal infrastructure** -- use the server as a proxy to interact with services not exposed to the public internet

Confirmed in live testing: the server successfully connected to the internal MySQL container (`172.19.0.3:3306`) and retrieved a full HTTP response from its own loopback interface (`127.0.0.1:80`).

---

### Remediation

1. **Use `NoPrivateNetworkHttpClient`** -- wrap the injected HTTP client with Symfony's built-in SSRF protection before passing it to feedIo: ```php use Symfony\Component\HttpClient\NoPrivateNetworkHttpClient;

$safeClient = new NoPrivateNetworkHttpClient($this->httpClient); ``` This blocks all RFC-1918, loopback, and link-local addresses at the HTTP client level.

2. **Validate URL scheme and host** -- before calling `feedIo->read()`, parse the URL and reject anything that is not `http://` or `https://` with a public routable IP or hostname.

3. **Configure the DCA field** -- add `'rgxp' => 'url'` and a custom validation callback to `tl_module.rss_feed` to reject non-public URLs at save time.

Are you affected?

Enter the version of the package you're using.

Affected packages

Packagist/contao/contao
Introduced in: 5.3.35Fixed in: 5.3.48
Fixcomposer require contao/contao:^5.3.48
Packagist/contao/contao
Introduced in: 5.4.0Fixed in: 5.7.9
Fixcomposer require contao/contao:^5.7.9
Packagist/contao/core-bundle
Introduced in: 5.3.35Fixed in: 5.3.48
Fixcomposer require contao/core-bundle:^5.3.48
Packagist/contao/core-bundle
Introduced in: 5.4.0Fixed in: 5.7.9
Fixcomposer require contao/core-bundle:^5.7.9

References