LP-2022-02: HTTP Host Header Attack Vulnerabilities
The package laminas/laminas-diactoros (Diactoros) is a PSR-7 HTTP Message and PSR-17 HTTP Message Factory implementation, providing HTTP request and response message representations both for making HTTP client requests and responding to HTTP requests server-side.
When responding to an incoming request and generating a ServerRequest
instance, the library takes into consideration a number of X-Forwarded-*
headers when generating a Uri
instance.
Doing so without validating the source of these headers against a list of known, trusted proxies can lead to HTTP Host Header attacks, which can potentially lead to XSS attacks (if a fully-qualified URL is used in links) and/or URL poisoning.
This functionality also affects our mezzio/mezzio-swoole package, as it utilizes functionality from Diactoros when generating a ServerRequest
from an incoming Swoole HTTP request.
Affected versions
- laminas/laminas-diactoros versions prior to 2.11.1
- mezzio/swoole versions prior to 3.7.0, and 4.x versions prior to 4.3.0
Action Taken
Starting in laminas/laminas-diactoros 2.11.1, we have added Laminas\Diactoros\ServerRequestFilter\FilterServerRequestInterface
, which defines the single method __invoke(Psr\Http\Message\ServerRequestInterface $request): Psr\Http\Message\ServerRequestInterface
.
Filters implementing this interface allow modifying and returning a generated ServerRequest
.
The primary use case of the interface is to allow modifying the generated URI based on the presence of headers such as X-Forwarded-Host
.
When operating behind a reverse proxy, the Host
header is often rewritten to the name of the node to which the request is being forwarded, and an X-Forwarded-Host
header is generated with the original Host
value to allow the server to determine the original host the request was intended for.
(We have always examined the X-Forwarded-Proto
header; as of Diactoros 2.11.1, we also examine the X-Forwarded-Port
header.)
To accommodate this use case, we created Laminas\Diactoros\ServerRequestFilter\FilterUsingXForwardedHeaders.
Due to potential security issues, it is generally best to only accept these headers if you trust the reverse proxy that has initiated the request.
(This value is found in $_SERVER['REMOTE_ADDR']
, which is present as $request->getServerParams()['REMOTE_ADDR']
within PSR-7 implementations.)
FilterUsingXForwardedHeaders
provides named constructors to allow you to trust these headers from any source (which has been the default behavior of Diactoros since the beginning), or to specify specific IP addresses or CIDR subnets to trust, along with which headers are trusted.
Laminas\Diactoros\ServerRequestFactory::fromGlobals()
was updated to accept a FilterServerRequestInterface
as an additional, optional argument.
Since the X-Forwarded-*
headers do have valid use cases, particularly in clustered environments using a load balancer, to prevent backwards compatibility breaks, if no filter is provided, we generate an instance via FilterUsingXForwardedHeaders::trustReservedSubnets()
, which generates an instance marked to trust only proxies on private subnets.
The mezzio/mezzio package, which is the foundation of all Mezzio applications, released a new version, 3.11.0, which now defines a Laminas\Diactoros\ServerRequestFilter\FilterServerRequestInterface
service, and consumes it in the Psr\Http\Message\ServerRequest
factory it defines (Mezzio\Container\ServerRequestFactoryFactory
), using the same defaults as the Diactoros package when none is defined.
It also provides a configuration-based approach for configuring the FilterUsingXForwardedHeaders
instance.
Finally, we have also updated mezzio/mezzio-swoole with releases 3.7.0 and 4.3.0 to consume a FilterServerRequestInterface
instance when generating a Laminas\Diactoros\ServerRequest
from an incoming Swoole\Http\Request
instance, using the same defaults as Diactoros.
Mitigations
To mitigate this vulnerability without updating your libraries, you can configure your web server to drop X-Forwarded-*
headers prior to passing the request on to PHP.
In Apache configuration, this requires mod_headers, and can be accomplished using RequestHeader unset
:
RequestHeader unset X-Forwarded-Host
RequestHeader unset X-Forwarded-Port
RequestHeader unset X-Forwarded-Proto
RequestHeader unset X-Forwarded-For
In nginx configuration:
fastcgi_param HTTP_X_FORWARDED_HOST '';
fastcgi_param HTTP_X_FORWARDED_PORT '';
fastcgi_param HTTP_X_FORWARDED_PROTO '';
fastcgi_param HTTP_X_FORWARDED_FOR '';
Acknowledgments
The Laminas Project thanks Maximilian Kresse for identifying the issue and validating our patches.
Released 2022-07-25
Have you identified a security vulnerability?
Please report it to us at security@getlaminas.org