You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<?php
// https://one.example.com
// possibly only with HTTP 2/3, didn't test with HTTP 1.0/1.1
if ( $_SERVER['REQUEST_URI'] === '/result/' || $_SERVER['REQUEST_URI'] === '/world/' ) {
echo "done";
exit;
}
// this is required to stop nginx from buffering the response (and otherwise this error doesn't happen)
header( 'X-Accel-Buffering: no' );
header( 'Location: https://one.example.com/result/', true, 302 );
register_shutdown_function( static function () {
register_shutdown_function( static function () {
// this is required, otherwise it won't happen
fastcgi_finish_request();
// the "sleep" is what creates the issue. A usleep for a shorter amount of time seems to be enough though (I don't think the sleep() is the origin of the issue though, it just allows other requests to be handled in the meantime, which causes this issue)
sleep( 1 );
// echoing arbitrary text with a space before any ":" will cause nginx to report:
// upstream sent invalid header: "a\x20..." while reading response header from upstream
// echo 'a b';
// outputting text that looks like a header, will set this as response header for the page we redirect to, but concatenates it randomly with a different response header (see screenshot)
echo 'X-Risky: yes';
} );
} );
When opening https://one.example.com, the echoed "X-Risky" suddenly shows up as a response header on the redirected URL, with the value randomly concatenated with another response header (actual one that the redirected request set)
If we echo something with a space before the :, e.g. echo 'a b'; nginx will instead report an error (on the redirected URL too!):
upstream sent invalid header: "a\x20..." while reading response header from upstream
What is potentially a security issue is, that it seems to apply that "X-Risky" to whatever next request is handled by nginx. Even if it is a different subdomain (didn't test it for different domain).
e.g. if I am on one.example.com (which is it's own server {} block in nginx), and set a Location: https://two.example.com (which is it's own server {} block in nginx too, but handled in the same nginx instance), then suddenly I get the X-Risky header in the response of two.example.com (and when using the "a b" I also get the upstream error in the two.example.com nginx error log)
It's not necessarily the "Location:" that this wrong header is applied too:
e.g. replace the "Location:" header in the example above with: Link: </world/>; as=fetch; crossorigin; rel=preload
when requesting the page in Chrome, the response for /world/ will suddenly have the X-Risky header
Even worse, it seems it's just whatever request is next handled by the nginx instance at the time AFTER the sleep gets that header applied (or the error reported), even if it's a completely different (sub)domain in a different server {} block
(this issue happens independent of request method, e.g. GET, POST,... all same)
The issue seems to be for responses that do not have a request body, but are headers only at the time when fastcgi_finish_request() is called.
This isn't a security issue in PHP itself (therefore not reported as such), but just an unclear and unexpected error.
It has been reported to nginx as a security advisory independently
PHP Version
PHP 8.3.17 (php-fpm) + nginx 1.27.1.1
Operating System
No response
The text was updated successfully, but these errors were encountered:
Additionally, the HTTP status code is incorrectly set too. If the redirection result would set HTTP status code 404, it will be returned with status 200 though
This sounds more like an issue in nginx to me. But it's possible that we are messing up the protocol as that echo 'X-Risky: yes'; should not be sent I would think. I need to debug it to see the fcgi records and what nginx is doing.
It has been reported to nginx as a security advisory independently
They're investigating (couldn't reproduce at first, since the important part was missing #18275 (comment))
However, from what it looks like, this isn't an issue in nginx (and nothing nginx can do about that), since for nginx the request is completed, since this is what PHP tells it.
Coincidentally, I found openresty/lua-nginx-module#156 - one could try to use that and see what happens when using ngx.say or ngx.header[...] after the ngx.eof and a sleep - and whether it would cause the same issue we see in PHP or not.
As the OP there writes though, this correct behavior has unwanted side-effects for PHP's fastcgi_finish_request():
Then if we open this url in browser and continuously refresh the page the first one would be served in ~1 ms, but all subsequent request can delay up to 1 second.
Description
When opening https://one.example.com, the echoed "X-Risky" suddenly shows up as a response header on the redirected URL, with the value randomly concatenated with another response header (actual one that the redirected request set)
If we echo something with a space before the :, e.g. echo 'a b'; nginx will instead report an error (on the redirected URL too!):
What is potentially a security issue is, that it seems to apply that "X-Risky" to whatever next request is handled by nginx. Even if it is a different subdomain (didn't test it for different domain).
e.g. if I am on one.example.com (which is it's own server {} block in nginx), and set a Location: https://two.example.com (which is it's own server {} block in nginx too, but handled in the same nginx instance), then suddenly I get the X-Risky header in the response of two.example.com (and when using the "a b" I also get the upstream error in the two.example.com nginx error log)
It's not necessarily the "Location:" that this wrong header is applied too:
e.g. replace the "Location:" header in the example above with:
Link: </world/>; as=fetch; crossorigin; rel=preload
when requesting the page in Chrome, the response for /world/ will suddenly have the X-Risky header
Even worse, it seems it's just whatever request is next handled by the nginx instance at the time AFTER the sleep gets that header applied (or the error reported), even if it's a completely different (sub)domain in a different server {} block
(this issue happens independent of request method, e.g. GET, POST,... all same)
The issue seems to be for responses that do not have a request body, but are headers only at the time when fastcgi_finish_request() is called.
I guess PHP
fastcgi_finish_request()
doesn't send an "EOF" https://github.com/openresty/lua-nginx-module?tab=readme-ov-file#ngxeofThis isn't a security issue in PHP itself (therefore not reported as such), but just an unclear and unexpected error.
It has been reported to nginx as a security advisory independently
PHP Version
PHP 8.3.17 (php-fpm) + nginx 1.27.1.1
Operating System
No response
The text was updated successfully, but these errors were encountered: