Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fastcgi_finish_request() doesn't eof requests with no body, allowing setting response headers for othe requests #18275

Open
kkmuffme opened this issue Apr 8, 2025 · 0 comments

Comments

@kkmuffme
Copy link

kkmuffme commented Apr 8, 2025

Description

<?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)

Image

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.

I guess PHP fastcgi_finish_request() doesn't send an "EOF" https://github.com/openresty/lua-nginx-module?tab=readme-ov-file#ngxeof

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants