Skip to content

Improve SimplePie redirects (POST to GET, remove authentication cross-origin)#78

Merged
Alkarex merged 16 commits into
FreshRSS:freshrssfrom
Inverle:improve-sp-redirects
Jun 27, 2026
Merged

Improve SimplePie redirects (POST to GET, remove authentication cross-origin)#78
Alkarex merged 16 commits into
FreshRSS:freshrssfrom
Inverle:improve-sp-redirects

Conversation

@Inverle

@Inverle Inverle commented May 30, 2026

Copy link
Copy Markdown
Member

Comment thread src/File.php Outdated
@Inverle Inverle requested a review from Alkarex June 2, 2026 21:59
@Alkarex

Alkarex commented Jun 5, 2026

Copy link
Copy Markdown
Member

Is it correctly understood that passing CURLOPT_FOLLOWLOCATION as a cURL option will allow skipping this new code?
I am interested in keeping an option to let cURL perform the work rather than using our or SimplePie's re-implementation

@Alkarex

Alkarex commented Jun 5, 2026

Copy link
Copy Markdown
Member

Although we only use the cURL path in FreshRSS, the fsockopen path further below should probably be updated in the same manner

@Inverle

Inverle commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

Is it correctly understood that passing CURLOPT_FOLLOWLOCATION as a cURL option will allow skipping this new code? I am interested in keeping an option to let cURL perform the work rather than using our or SimplePie's re-implementation

Not yet, because CURLOPT_FOLLOWLOCATION is forced to false:

curl_setopt($fp, CURLOPT_FOLLOWLOCATION, false); // FreshRSS

@Alkarex

Alkarex commented Jun 11, 2026

Copy link
Copy Markdown
Member

I have tried to apply the same logic to the fsockopen path. But not tested. Checks welcome

@Inverle

Inverle commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

I have tried to apply the same logic to the fsockopen path. But not tested. Checks welcome

Testing with a patch like this in FreshRSS, I can confirm the auth headers are removed correctly after a redirect:

diff --git a/app/Models/SimplePieFetch.php b/app/Models/SimplePieFetch.php
index 1a0737b3d..2e41d87d7 100644
--- a/app/Models/SimplePieFetch.php
+++ b/app/Models/SimplePieFetch.php
@@ -31,7 +31,13 @@ final class FreshRSS_SimplePieFetch extends \SimplePie\File
                } elseif ($redirects < 0) {
                        $redirects = -1; // infinite redirects
                }
-               parent::__construct($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options);
+               if (!isset($GLOBALS['fsockopen_test'])) {
+                       $GLOBALS['fsockopen_test'] = 1;
+                       $headers['Cookie'] = 'test=123';
+                       $headers['Authorization'] = 'test';
+                       $headers['X-SomeOtherHeader'] = '123';
+               }
+               parent::__construct($url, $timeout, $redirects, $headers, $useragent, true, $curl_options);
        }
 
        #[\Override]

@Alkarex

Alkarex commented Jun 13, 2026

Copy link
Copy Markdown
Member

If possible, I would still like a way, for instance by passing a CURLOPT_FOLLOWLOCATION, to use the native cURL handing of redirections

@Alkarex

Alkarex commented Jun 21, 2026

Copy link
Copy Markdown
Member

@Alkarex

Alkarex commented Jun 24, 2026

Copy link
Copy Markdown
Member

FYI, I was hopping to propose a little update, but got sidetracked and still have not found an approach I like much. I have a work in progress for probably in a couple of days, but additional inputs welcome, in particular to avoid breaking features as posted just above if possible

@Alkarex

Alkarex commented Jun 27, 2026

Copy link
Copy Markdown
Member

I have made an attempt. What do you think @Inverle ?

  • CURLOPT_FOLLOWLOCATION not set or null: SimplePie PHP handling of HTTP redirections with security checks (default, recommended).
  • CURLOPT_FOLLOWLOCATION = truthy: Native cURL handling of HTTP redirections without SimplePie security checks;
  • CURLOPT_FOLLOWLOCATION = falsy: No HTTP redirections at all;

And it is up to the client to allow CURLOPT_FOLLOWLOCATION == true or not.

Comment thread src/File.php Outdated
Comment on lines +180 to +184
// Native cURL redirect handling
$finalUrl = curl_getinfo($fp, CURLINFO_EFFECTIVE_URL);
if (is_string($finalUrl) && $finalUrl !== '') {
$this->url = $finalUrl;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that original SimplePie does not have this code, even though it should be needed for get_final_requested_uri()?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed

@Inverle

Inverle commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

I have made an attempt. What do you think @Inverle ?

* CURLOPT_FOLLOWLOCATION not set or null: SimplePie PHP handling of HTTP redirections with security checks (default, recommended).

* CURLOPT_FOLLOWLOCATION = truthy: Native cURL handling of HTTP redirections _without_ SimplePie security checks;

* CURLOPT_FOLLOWLOCATION = falsy: No HTTP redirections at all;

And it is up to the client to allow CURLOPT_FOLLOWLOCATION == true or not.

It is fine. I have also made this commit: FreshRSS/FreshRSS@e698ae9

@Alkarex Alkarex merged commit 2d4c5b9 into FreshRSS:freshrss Jun 27, 2026
11 checks passed
Alkarex added a commit to Inverle/FreshRSS that referenced this pull request Jun 27, 2026
@Inverle Inverle deleted the improve-sp-redirects branch June 28, 2026 07:55
Alkarex added a commit to FreshRSS/FreshRSS that referenced this pull request Jun 28, 2026
* Add SSRF mitigations using `filter_var` and `CURLOPT_RESOLVE`
The idea is to prevent FreshRSS from sending any HTTP requests to internal services, except for the ones that are explicitly allowed in the config.

Based on https://github.com/moodle/moodle/blob/6e82b46a480826d1a85394d9e5087f7d82d1dd52/lib/filelib.php#L3818 and https://github.com/symfony/symfony/blob/8.1/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

FreshRSS/simplepie#76
FreshRSS/simplepie#78

* Add allowlist setting in Web UI

* make readme

* Update app/i18n/fr/admin.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme again

* make readme

* Further work

Still WIP and needs testing etc.

* Readd previous if check for domain combination allowlist

* Turn POST to GET after redirect

* Improve

* Update config.default.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme

* Skip SSRF check if `CURLOPT_PROXY` is set

* make readme

* Fix `!empty()` mistake

* Respect max redirects feed option when fetching with `httpGet()`

* Respect max redirects during SimplePie fetching + fix bypass

bypass fix: `CURLOPT_FOLLOWLOCATION` was moved below so that emulated redirects are enforced.

* Avoid FreshRSS and Minz code in SimplePie
#8400 (comment)

* Corrected hook code

* phpdoc wrong return type

* Add CIDR support in allowlist

* Implement simple DNS caching

* Suppress `dns_get_record()` warnings

* A bit of proof-reading

* Minor typo

* Fix proxy logic

* Fix HTTP POST redirect logic

* Proofread checkCIDR
Add fixes for several situations

* Remove credentials from URL in logs

* Ensure `CURLOPT_FOLLOWLOCATION` is `false` by setting it at the end

* Fix codesniffer long line

* Fix potential bypass due to wrong return value

If there were no records returned by `dns_get_record()`, no overrides to `CURLOPT_RESOLVE` would get passed,
and a potential bypass could occur, when cURL would try to resolve the domain by itself.

* Put the URL at the end in logs

* Add documentation and environment variable support

* make readme

* Fix wrong behavior in case of IP

* Fix duplicate selector in CSS

* Minor type check change

* i18n fr, en

* Minor type check change

* Fix whitespace i18n fr

* make fix-all

* Fix `$ips_ok` not being returned after domain records were cached

* make readme

* PHPStan fix

* make readme

* Minor syntax in SimplePie

* Only return `null` if no allowed IPs were found

* Add wildcard *, help message

* Consistent docs with help message

* i18n: pl

* SimplePie compatibility PHP 7.2

* make fix-all

* Sync SimplePie
* FreshRSS/simplepie#76

* 💥 Breaking change in the Changelog

* Document `INTERNAL_HOST_ALLOWLIST` in Docker docs

* Remove `Cookie` and `Authorization` headers in `httpGet()` during cross-origin redirect

* Minor whitespace
And same comment convention than below

* Remove authentication headers and change POST to GET on redirect in SimplePie

* Remove .local in Docker example

* Fill in default ports when comparing URL origins

* Remove .local from other places than the Docker example

* Rewrite WebSub subscribe to use `httpGet()`

* make fix-all

* Also unset `CURLOPT_USERPWD` during redirects

* phpcs fix

* Always unset `CURLOPT_FOLLOWLOCATION`

* Bump SimplePie
FreshRSS/simplepie#78

* Update logic for CURLOPT_FOLLOWLOCATION

* Fix PHPStan

* Changelog fix security section

* Update most common RSS Bridge case
https://hub.docker.com/r/rssbridge/rss-bridge

* Replace misleading 127.0.0.1:8080 example for Docker
This does not make sense for a Docker container

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants