-
-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request from GHSA-8274-h5jp-97vr
Security/x forwarded header trust
- Loading branch information
Showing
20 changed files
with
1,522 additions
and
190 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Preparing for Version 3 | ||
|
||
## ServerRequestFilterInterface defaults | ||
|
||
Introduced in version 2.11.1, the `Laminas\Diactoros\ServerRequestFilter\FilterServerRequestInterface` is used by `ServerRequestFactory::fromGlobals()` to allow modifying the generated `ServerRequest` instance prior to returning it. | ||
The primary use case 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 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. | ||
To prevent backwards compatibility breaks, we use this filter by default, marked to trust **only proxies on private subnets**. | ||
|
||
Features will be added to the 3.11.0 version of [mezzio/mezzio](https://github.com/mezzio/mezzio) that will allow configuring the `Laminas\Diactoros\ServerRequestFilter\FilterServerRequestInterface` instance, and we recommend explicitly configuring this to utilize the `FilterUsingXForwardedHeaders` if you depend on this functionality. | ||
If you **do not** need the functionality, we recommend specifying `Laminas\Diactoros\ServerRequestFilter\DoNotFilter` as the configured `FilterServerRequestInterface` in your application immediately. | ||
|
||
We will update this documentation with a link to the related functionality in mezzio/mezzio when it is published. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Server Request Filters | ||
|
||
INFO: **New Feature** | ||
Available since version 2.11.1 | ||
|
||
Server request filters allow you to modify the initial state of a generated `ServerRequest` instance as returned from `Laminas\Diactoros\ServerRequestFactory::fromGlobals()`. | ||
Common use cases include: | ||
|
||
- Generating and injecting a request ID. | ||
- Modifying the request URI based on headers provided (e.g., based on the `X-Forwarded-Host` or `X-Forwarded-Proto` headers). | ||
|
||
## FilerServerRequestInterface | ||
|
||
A request filter implements `Laminas\Diactoros\ServerRequestFilter\FilterServerRequestInterface`: | ||
|
||
```php | ||
namespace Laminas\Diactoros\ServerRequestFilter; | ||
|
||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
interface FilterServerRequestInterface | ||
{ | ||
public function __invoke(ServerRequestInterface $request): ServerRequestInterface; | ||
} | ||
``` | ||
|
||
## Implementations | ||
|
||
We provide the following implementations: | ||
|
||
- `DoNotFilter`: returns the provided `$request` verbatim. | ||
- `FilterUsingXForwardedHeaders`: if the originating request comes from a trusted proxy, examines the `X-Forwarded-*` headers, and returns the request instance with a URI instance that reflects those headers. | ||
|
||
### DoNotFilter | ||
|
||
This filter returns the `$request` argument back verbatim when invoked. | ||
|
||
### FilterUsingXForwardedHeaders | ||
|
||
Servers behind a reverse proxy need mechanisms to determine the original URL requested. | ||
As such, reverse proxies have provided a number of mechanisms for delivering this information, with the use of `X-Forwarded-*` headers being the most prevalant. | ||
These include: | ||
|
||
- `X-Forwarded-Host`: the original `Host` header value. | ||
- `X-Forwarded-Port`: the original port included in the `Host` header value. | ||
- `X-Forwarded-Proto`: the original URI scheme used to make the request (e.g., "http" or "https"). | ||
|
||
`Laminas\Diactoros\ServerRequestFilter\FilterUsingXForwardedHeaders` provides named constructors for choosing whether to never trust proxies, always trust proxies, or choose wich proxies and/or headers to trust in order to modify the URI composed in the request instance to match the original request. | ||
These named constructors are: | ||
|
||
- `FilterUsingXForwardedHeadersFactory::trustProxies(string[] $proxyCIDRList, string[] $trustedHeaders = FilterUsingXForwardedHeaders::X_FORWARDED_HEADERS): void`: when this method is called, only requests originating from the trusted proxy/ies will be considered, as well as only the headers specified. | ||
Proxies may be specified by IP address, or using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) for subnets; both IPv4 and IPv6 are accepted. | ||
The special string "*" will be translated to two entries, `0.0.0.0/0` and `::/0`. | ||
- `FilterUsingXForwardedHeaders::trustAny(): void`: when this method is called, the filter will trust requests from any origin, and use any of the above headers to modify the URI instance. | ||
It is functionally equivalent to `FilterUsingXForwardedHeaders::trustProxies(['*'])`. | ||
- `FilterUsingXForwardedHeaders::trustReservedSubnets(): void`: when this method is called, the filter will trust requests made from reserved, private subnets. | ||
It is functionally equivalent to `FilterUsingXForwardedHeaders::trustProxies()` with the following elements in the `$proxyCIDRList`: | ||
- 10.0.0.0/8 | ||
- 127.0.0.0/8 | ||
- 172.16.0.0/12 | ||
- 192.168.0.0/16 | ||
- ::1/128 (IPv6 localhost) | ||
- fc00::/7 (IPv6 private networks) | ||
- fe80::/10 (IPv6 local-link addresses) | ||
|
||
Internally, the filter checks the `REMOTE_ADDR` server parameter (as retrieved from `getServerParams()`) and compares it against each proxy listed; the first to match indicates trust. | ||
|
||
#### Constants | ||
|
||
The `FilterUsingXForwardedHeaders` defines the following constants for use in specifying various headers: | ||
|
||
- `HEADER_HOST`: corresponds to `X-Forwarded-Host`. | ||
- `HEADER_PORT`: corresponds to `X-Forwarded-Port`. | ||
- `HEADER_PROTO`: corresponds to `X-Forwarded-Proto`. | ||
|
||
#### Example usage | ||
|
||
Trusting all `X-Forwarded-*` headers from any source: | ||
|
||
```php | ||
$filter = FilterUsingXForwardedHeaders::trustAny(); | ||
``` | ||
|
||
Trusting only the `X-Forwarded-Host` header from any source: | ||
|
||
```php | ||
$filter = FilterUsingXForwardedHeaders::trustProxies('0.0.0.0/0', [FilterUsingXForwardedHeaders::HEADER_HOST]); | ||
``` | ||
|
||
Trusting the `X-Forwarded-Host` and `X-Forwarded-Proto` headers from a single Class C subnet: | ||
|
||
```php | ||
$filter = FilterUsingXForwardedHeaders::trustProxies( | ||
'192.168.1.0/24', | ||
[FilterUsingXForwardedHeaders::HEADER_HOST, FilterUsingXForwardedHeaders::HEADER_PROTO] | ||
); | ||
``` | ||
|
||
Trusting the `X-Forwarded-Host` header from either a Class A or a Class C subnet: | ||
|
||
```php | ||
$filter = FilterUsingXForwardedHeaders::trustProxies( | ||
['10.1.1.0/16', '192.168.1.0/24'], | ||
[FilterUsingXForwardedHeaders::HEADER_HOST, FilterUsingXForwardedHeaders::HEADER_PROTO] | ||
); | ||
``` | ||
|
||
Trusting any `X-Forwarded-*` header from any private subnet: | ||
|
||
```php | ||
$filter = FilterUsingXForwardedHeaders::trustReservedSubnets(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Diactoros\Exception; | ||
|
||
use Laminas\Diactoros\ServerRequestFilter\FilterUsingXForwardedHeaders; | ||
|
||
class InvalidForwardedHeaderNameException extends RuntimeException implements ExceptionInterface | ||
{ | ||
/** @param mixed $name */ | ||
public static function forHeader($name): self | ||
{ | ||
if (! is_string($name)) { | ||
$name = sprintf('(value of type %s)', is_object($name) ? get_class($name) : gettype($name)); | ||
} | ||
|
||
return new self(sprintf( | ||
'Invalid X-Forwarded-* header name "%s" provided to %s', | ||
$name, | ||
FilterUsingXForwardedHeaders::class | ||
)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Diactoros\Exception; | ||
|
||
class InvalidProxyAddressException extends RuntimeException implements ExceptionInterface | ||
{ | ||
/** @param mixed $proxy */ | ||
public static function forInvalidProxyArgument($proxy): self | ||
{ | ||
$type = is_object($proxy) ? get_class($proxy) : gettype($proxy); | ||
return new self(sprintf( | ||
'Invalid proxy of type "%s" provided;' | ||
. ' must be a valid IPv4 or IPv6 address, optionally with a subnet mask provided' | ||
. ' or an array of such values', | ||
$type, | ||
)); | ||
} | ||
|
||
public static function forAddress(string $address): self | ||
{ | ||
return new self(sprintf( | ||
'Invalid proxy address "%s" provided;' | ||
. ' must be a valid IPv4 or IPv6 address, optionally with a subnet mask provided', | ||
$address, | ||
)); | ||
} | ||
} |
Oops, something went wrong.