Make encoded character options opt-in
This commit is contained in:
parent
ee265a8509
commit
adf47fba31
19 changed files with 221 additions and 179 deletions
|
|
@ -87,10 +87,10 @@ Complete documentation is available at https://traefik.io`,
|
||||||
func runCmd(staticConfiguration *static.Configuration) error {
|
func runCmd(staticConfiguration *static.Configuration) error {
|
||||||
configureLogging(staticConfiguration)
|
configureLogging(staticConfiguration)
|
||||||
|
|
||||||
// Display warning to advertise for new behavior of rejecting encoded characters in the request path.
|
log.WithoutContext().Warn("Traefik can reject some encoded characters in the request path." +
|
||||||
// Deprecated: this has to be removed in the next minor/major version.
|
"When your backend is not fully compliant with [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986)," +
|
||||||
log.WithoutContext().Warnf("Starting with v2.11.32, Traefik now rejects some encoded characters in the request path by default. " +
|
"it is recommended to set these options to `false` to avoid split-view situation." +
|
||||||
"Refer to the documentation for more details: https://doc.traefik.io/traefik/v2.11/migration/v2/#encoded-characters-in-request-path")
|
"Refer to the documentation for more details: https://doc.traefik.io/traefik/v2.11/migration/v2/#encoded-characters-configuration-default-values")
|
||||||
|
|
||||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -737,3 +737,30 @@ Note: This check is not done against query parameters,
|
||||||
but only against the request path as defined in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
|
but only against the request path as defined in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
|
||||||
|
|
||||||
Please check out the entrypoint [encodedCharacters option](../routing/entrypoints.md#encoded-characters) documentation for more details.
|
Please check out the entrypoint [encodedCharacters option](../routing/entrypoints.md#encoded-characters) documentation for more details.
|
||||||
|
|
||||||
|
## v2.11.35
|
||||||
|
|
||||||
|
### Encoded Characters Configuration Default Values
|
||||||
|
|
||||||
|
Since `v2.11.35`, the options for encoded characters now have a `true` default value.
|
||||||
|
This means that Traefik will not reject requests with a path containing a specific set of encoded characters by default.
|
||||||
|
It is now up to the users to configure the security hardening of encoded characters.
|
||||||
|
|
||||||
|
Here is the list of the encoded characters that can be configured to `false` to disallow them:
|
||||||
|
|
||||||
|
| Encoded Character | Character | Config options | Default value |
|
||||||
|
|-------------------|-------------------------|--------------------------------------------------------------------------------------|---------------|
|
||||||
|
| `%2f` or `%2F` | `/` (slash) | `entryPoints.<name>`<br/>`.http.encodedCharacters`<br/>`.allowEncodedSlash` | `true` |
|
||||||
|
| `%5c` or `%5C` | `\` (backslash) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedBackSlash` | `true` |
|
||||||
|
| `%00` | `NULL` (null character) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedNullCharacter` | `true` |
|
||||||
|
| `%3b` or `%3B` | `;` (semicolon) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedSemicolon` | `true` |
|
||||||
|
| `%25` | `%` (percent) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedPercent` | `true` |
|
||||||
|
| `%3f` or `%3F` | `?` (question mark) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedQuestionMark` | `true` |
|
||||||
|
| `%23` | `#` (hash) | `entryPoints.<name>.`<br/>`.http.encodedCharacters`<br/>`.allowEncodedHash` | `true` |
|
||||||
|
|
||||||
|
Note: This check is not done against query parameters,
|
||||||
|
but only against the request path as defined
|
||||||
|
in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
|
||||||
|
|
||||||
|
Please check out the entrypoint [encodedCharacters option](../routing/entrypoints.md#encoded-characters) documentation
|
||||||
|
for more details.
|
||||||
|
|
|
||||||
|
|
@ -124,25 +124,25 @@ Trust only forwarded headers from selected IPs.
|
||||||
HTTP configuration.
|
HTTP configuration.
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedbackslash`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedbackslash`:
|
||||||
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedhash`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedhash`:
|
||||||
Defines whether requests with encoded hash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded hash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodednullcharacter`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodednullcharacter`:
|
||||||
Defines whether requests with encoded null characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded null characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedpercent`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedpercent`:
|
||||||
Defines whether requests with encoded percent characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded percent characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedquestionmark`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedquestionmark`:
|
||||||
Defines whether requests with encoded question mark characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded question mark characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedsemicolon`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedsemicolon`:
|
||||||
Defines whether requests with encoded semicolon characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded semicolon characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedslash`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedslash`:
|
||||||
Defines whether requests with encoded slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded slash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodequerysemicolons`:
|
`--entrypoints.<name>.http.encodequerysemicolons`:
|
||||||
Defines whether request query semicolons should be URLEncoded. (Default: ```false```)
|
Defines whether request query semicolons should be URLEncoded. (Default: ```false```)
|
||||||
|
|
|
||||||
|
|
@ -133,25 +133,25 @@ HTTP/3 configuration. (Default: ```false```)
|
||||||
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
|
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDBACKSLASH`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDBACKSLASH`:
|
||||||
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDHASH`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDHASH`:
|
||||||
Defines whether requests with encoded hash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded hash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDNULLCHARACTER`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDNULLCHARACTER`:
|
||||||
Defines whether requests with encoded null characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded null characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDPERCENT`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDPERCENT`:
|
||||||
Defines whether requests with encoded percent characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded percent characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDQUESTIONMARK`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDQUESTIONMARK`:
|
||||||
Defines whether requests with encoded question mark characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded question mark characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSEMICOLON`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSEMICOLON`:
|
||||||
Defines whether requests with encoded semicolon characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded semicolon characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSLASH`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSLASH`:
|
||||||
Defines whether requests with encoded slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded slash characters in the path are allowed. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEQUERYSEMICOLONS`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEQUERYSEMICOLONS`:
|
||||||
Defines whether request query semicolons should be URLEncoded. (Default: ```false```)
|
Defines whether request query semicolons should be URLEncoded. (Default: ```false```)
|
||||||
|
|
|
||||||
|
|
@ -129,13 +129,13 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||||
- "192.168.0.1"
|
- "192.168.0.1"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedSlash: true
|
allowEncodedSlash: false
|
||||||
allowEncodedBackSlash: true
|
allowEncodedBackSlash: false
|
||||||
allowEncodedNullCharacter: true
|
allowEncodedNullCharacter: false
|
||||||
allowEncodedSemicolon: true
|
allowEncodedSemicolon: false
|
||||||
allowEncodedPercent: true
|
allowEncodedPercent: false
|
||||||
allowEncodedQuestionMark: true
|
allowEncodedQuestionMark: false
|
||||||
allowEncodedHash: true
|
allowEncodedHash: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -162,13 +162,13 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||||
insecure = true
|
insecure = true
|
||||||
trustedIPs = ["127.0.0.1", "192.168.0.1"]
|
trustedIPs = ["127.0.0.1", "192.168.0.1"]
|
||||||
[entryPoints.name.http.encodedCharacters]
|
[entryPoints.name.http.encodedCharacters]
|
||||||
allowEncodedSlash = true
|
allowEncodedSlash = false
|
||||||
allowEncodedBackSlash = true
|
allowEncodedBackSlash = false
|
||||||
allowEncodedNullCharacter = true
|
allowEncodedNullCharacter = false
|
||||||
allowEncodedSemicolon = true
|
allowEncodedSemicolon = false
|
||||||
allowEncodedPercent = true
|
allowEncodedPercent = false
|
||||||
allowEncodedQuestionMark = true
|
allowEncodedQuestionMark = false
|
||||||
allowEncodedHash = true
|
allowEncodedHash = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
|
|
@ -185,13 +185,13 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||||
--entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
|
--entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
|
||||||
--entryPoints.name.forwardedHeaders.insecure=true
|
--entryPoints.name.forwardedHeaders.insecure=true
|
||||||
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
|
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedSlash=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedSlash=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedBackSlash=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedBackSlash=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedNullCharacter=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedNullCharacter=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedSemicolon=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedSemicolon=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedPercent=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedPercent=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedQuestionMark=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedQuestionMark=false
|
||||||
--entryPoints.name.http.encodedCharacters.allowEncodedHash=true
|
--entryPoints.name.http.encodedCharacters.allowEncodedHash=false
|
||||||
```
|
```
|
||||||
|
|
||||||
### Address
|
### Address
|
||||||
|
|
@ -1021,20 +1021,21 @@ entryPoints:
|
||||||
### Encoded Characters
|
### Encoded Characters
|
||||||
|
|
||||||
You can configure Traefik to control the handling of encoded characters in request paths for security purposes.
|
You can configure Traefik to control the handling of encoded characters in request paths for security purposes.
|
||||||
By default, Traefik rejects requests with path containing certain encoded characters that could be used in path traversal or other security attacks.
|
By default, Traefik do not reject requests with path containing certain encoded characters that could be used in path traversal or other security attacks.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
This check is not done against the request query parameters,
|
This check is not done against the request query parameters,
|
||||||
but only against the request path as defined in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
|
but only against the request path as defined in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
|
||||||
|
|
||||||
!!! warning "Security Considerations"
|
!!! info "Security Considerations"
|
||||||
|
|
||||||
Allowing certain encoded characters may expose your application to security vulnerabilities.
|
When your backend is not fully compliant with [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) and notably decode encoded reserved characters in the requets path,
|
||||||
|
it is recommended to set these options to `false` to avoid split-view situation and helps prevent path traversal attacks or other malicious attempts to bypass security controls.
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedSlash`"
|
??? info "`encodedCharacters.allowEncodedSlash`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded slash characters (`%2F` or `%2f`) in the path are allowed.
|
Controls whether requests with encoded slash characters (`%2F` or `%2f`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1045,7 +1046,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedSlash: true
|
allowEncodedSlash: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1055,18 +1056,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedSlash = true
|
allowEncodedSlash = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedSlash=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedSlash=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedBackSlash`"
|
??? info "`encodedCharacters.allowEncodedBackSlash`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded back slash characters (`%5C` or `%5c`) in the path are allowed.
|
Controls whether requests with encoded back slash characters (`%5C` or `%5c`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1077,7 +1078,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedBackSlash: true
|
allowEncodedBackSlash: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1087,18 +1088,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedBackSlash = true
|
allowEncodedBackSlash = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedBackSlash=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedBackSlash=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedNullCharacter`"
|
??? info "`encodedCharacters.allowEncodedNullCharacter`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded null characters (`%00`) in the path are allowed.
|
Controls whether requests with encoded null characters (`%00`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1109,7 +1110,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedNullCharacter: true
|
allowEncodedNullCharacter: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1119,18 +1120,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedNullCharacter = true
|
allowEncodedNullCharacter = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedNullCharacter=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedNullCharacter=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedSemicolon`"
|
??? info "`encodedCharacters.allowEncodedSemicolon`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded semicolon characters (`%3B` or `%3b`) in the path are allowed.
|
Controls whether requests with encoded semicolon characters (`%3B` or `%3b`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1141,7 +1142,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedSemicolon: true
|
allowEncodedSemicolon: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1151,18 +1152,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedSemicolon = true
|
allowEncodedSemicolon = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedSemicolon=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedSemicolon=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedPercent`"
|
??? info "`encodedCharacters.allowEncodedPercent`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded percent characters (`%25`) in the path are allowed.
|
Controls whether requests with encoded percent characters (`%25`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1173,7 +1174,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedPercent: true
|
allowEncodedPercent: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1183,18 +1184,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedPercent = true
|
allowEncodedPercent = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedPercent=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedPercent=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedQuestionMark`"
|
??? info "`encodedCharacters.allowEncodedQuestionMark`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded question mark characters (`%3F` or `%3f`) in the path are allowed.
|
Controls whether requests with encoded question mark characters (`%3F` or `%3f`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1205,7 +1206,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedQuestionMark: true
|
allowEncodedQuestionMark: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1215,18 +1216,18 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedQuestionMark = true
|
allowEncodedQuestionMark = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedQuestionMark=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedQuestionMark=false
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`encodedCharacters.allowEncodedHash`"
|
??? info "`encodedCharacters.allowEncodedHash`"
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=true_
|
||||||
|
|
||||||
Controls whether requests with encoded hash characters (`%23`) in the path are allowed.
|
Controls whether requests with encoded hash characters (`%23`) in the path are allowed.
|
||||||
|
|
||||||
|
|
@ -1237,7 +1238,7 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address: ":80"
|
address: ":80"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedHash: true
|
allowEncodedHash: false
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
@ -1247,13 +1248,13 @@ By default, Traefik rejects requests with path containing certain encoded charac
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.web.http.encodedCharacters]
|
[entryPoints.web.http.encodedCharacters]
|
||||||
allowEncodedHash = true
|
allowEncodedHash = false
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
## Static configuration
|
## Static configuration
|
||||||
--entryPoints.web.address=:80
|
--entryPoints.web.address=:80
|
||||||
--entryPoints.web.http.encodedCharacters.allowEncodedHash=true
|
--entryPoints.web.http.encodedCharacters.allowEncodedHash=false
|
||||||
```
|
```
|
||||||
|
|
||||||
### SanitizePath
|
### SanitizePath
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ When Traefik receives an HTTP request, it processes the request path through sev
|
||||||
|
|
||||||
Traefik inspects the path for potentially dangerous encoded characters and rejects requests containing them unless explicitly allowed.
|
Traefik inspects the path for potentially dangerous encoded characters and rejects requests containing them unless explicitly allowed.
|
||||||
|
|
||||||
Here is the list of the encoded characters that are rejected by default:
|
Here is the list of the encoded characters that are allowed by default:
|
||||||
|
|
||||||
| Encoded Character | Character |
|
| Encoded Character | Character |
|
||||||
|-------------------|-------------------------|
|
|-------------------|-------------------------|
|
||||||
|
|
@ -87,7 +87,12 @@ Configure it in the [EntryPoints](../routing/entrypoints.md#encoded-characters)
|
||||||
|
|
||||||
This filtering occurs before path sanitization and catches attack attempts that use encoding to bypass other security controls.
|
This filtering occurs before path sanitization and catches attack attempts that use encoding to bypass other security controls.
|
||||||
|
|
||||||
All encoded character filtering is enabled by default (`false` means encoded characters are rejected), providing maximum security:
|
All encoded character filtering is disabled by default (`true` means encoded characters are allowed).
|
||||||
|
|
||||||
|
!!! info "Security Considerations"
|
||||||
|
|
||||||
|
When your backend is not fully compliant with [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) and notably decode encoded reserved characters in the requets path,
|
||||||
|
it is recommended to set these options to `false` to avoid split-view situation and helps prevent path traversal attacks or other malicious attempts to bypass security controls.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
entryPoints:
|
entryPoints:
|
||||||
|
|
@ -95,13 +100,13 @@ entryPoints:
|
||||||
address: ":443"
|
address: ":443"
|
||||||
http:
|
http:
|
||||||
encodedCharacters:
|
encodedCharacters:
|
||||||
allowEncodedSlash: false # %2F - Default: false (RECOMMENDED)
|
allowEncodedSlash: false # %2F - Default: true
|
||||||
allowEncodedBackSlash: false # %5C - Default: false (RECOMMENDED)
|
allowEncodedBackSlash: false # %5C - Default: true
|
||||||
allowEncodedNullCharacter: false # %00 - Default: false (RECOMMENDED)
|
allowEncodedNullCharacter: false # %00 - Default: true
|
||||||
allowEncodedSemicolon: false # %3B - Default: false (RECOMMENDED)
|
allowEncodedSemicolon: false # %3B - Default: true
|
||||||
allowEncodedPercent: false # %25 - Default: false (RECOMMENDED)
|
allowEncodedPercent: false # %25 - Default: true
|
||||||
allowEncodedQuestionMark: false # %3F - Default: false (RECOMMENDED)
|
allowEncodedQuestionMark: false # %3F - Default: true
|
||||||
allowEncodedHash: false # %23 - Default: false (RECOMMENDED)
|
allowEncodedHash: false # %23 - Default: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,18 @@
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.strict]
|
[entryPoints.strict]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
# Default: no encoded characters allowed
|
[entryPoints.strict.http.encodedCharacters]
|
||||||
|
allowEncodedSlash = false
|
||||||
|
|
||||||
[entryPoints.permissive]
|
[entryPoints.permissive]
|
||||||
address = ":8001"
|
address = ":8001"
|
||||||
|
# No config, default values should apply
|
||||||
|
|
||||||
|
[entryPoints.permissive2]
|
||||||
|
address = ":8002"
|
||||||
|
# No config for allowEncodedSlash, default value is effectively true
|
||||||
[entryPoints.permissive.http.encodedCharacters]
|
[entryPoints.permissive.http.encodedCharacters]
|
||||||
allowEncodedSlash = true
|
allowEncodedBackSlash = false
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.web]
|
[entryPoints.web]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
[entryPoints.web.http.encodedCharacters]
|
|
||||||
allowEncodedSlash = true
|
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
|
||||||
|
|
@ -1520,6 +1520,12 @@ func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
|
||||||
target: "127.0.0.1:8001", // permissive entry point
|
target: "127.0.0.1:8001", // permissive entry point
|
||||||
expected: http.StatusOK,
|
expected: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Encoded slash should be ALLOWED on permissive2 entry point",
|
||||||
|
request: "GET /path%2Fwith%2Fslash HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||||
|
target: "127.0.0.1:8002", // permissive2 entry point
|
||||||
|
expected: http.StatusOK,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Regular path should work on strict entry point",
|
desc: "Regular path should work on strict entry point",
|
||||||
request: "GET /regular/path HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
request: "GET /regular/path HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||||
|
|
@ -1532,6 +1538,12 @@ func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
|
||||||
target: "127.0.0.1:8001",
|
target: "127.0.0.1:8001",
|
||||||
expected: http.StatusOK,
|
expected: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Regular path should work on permissive2 entry point",
|
||||||
|
request: "GET /regular/path HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||||
|
target: "127.0.0.1:8002",
|
||||||
|
expected: http.StatusOK,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,14 @@ type Service struct {
|
||||||
|
|
||||||
// Router holds the router configuration.
|
// Router holds the router configuration.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||||
DeniedEncodedPathCharacters RouterDeniedEncodedPathCharacters `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
DeniedEncodedPathCharacters *RouterDeniedEncodedPathCharacters `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
|
||||||
|
|
@ -1035,7 +1035,11 @@ func (in *Router) DeepCopyInto(out *Router) {
|
||||||
*out = new(RouterTLSConfig)
|
*out = new(RouterTLSConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
out.DeniedEncodedPathCharacters = in.DeniedEncodedPathCharacters
|
if in.DeniedEncodedPathCharacters != nil {
|
||||||
|
in, out := &in.DeniedEncodedPathCharacters, &out.DeniedEncodedPathCharacters
|
||||||
|
*out = new(RouterDeniedEncodedPathCharacters)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,16 @@ type EncodedCharacters struct {
|
||||||
AllowEncodedHash bool `description:"Defines whether requests with encoded hash characters in the path are allowed." json:"allowEncodedHash,omitempty" toml:"allowEncodedHash,omitempty" yaml:"allowEncodedHash,omitempty" export:"true"`
|
AllowEncodedHash bool `description:"Defines whether requests with encoded hash characters in the path are allowed." json:"allowEncodedHash,omitempty" toml:"allowEncodedHash,omitempty" yaml:"allowEncodedHash,omitempty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *EncodedCharacters) SetDefaults() {
|
||||||
|
ec.AllowEncodedSlash = true
|
||||||
|
ec.AllowEncodedBackSlash = true
|
||||||
|
ec.AllowEncodedNullCharacter = true
|
||||||
|
ec.AllowEncodedSemicolon = true
|
||||||
|
ec.AllowEncodedPercent = true
|
||||||
|
ec.AllowEncodedQuestionMark = true
|
||||||
|
ec.AllowEncodedHash = true
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP2Config is the HTTP2 configuration of an entry point.
|
// HTTP2Config is the HTTP2 configuration of an entry point.
|
||||||
type HTTP2Config struct {
|
type HTTP2Config struct {
|
||||||
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
|
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
if m.DeniedEncodedPathCharacters != nil {
|
if m.DeniedEncodedPathCharacters != nil {
|
||||||
// As the denied encoded path characters option is not configurable at the router level,
|
// As the denied encoded path characters option is not configurable at the router level,
|
||||||
// we can simply copy the whole structure to override the router's default config.
|
// we can simply copy the whole structure to override the router's default config.
|
||||||
cp.DeniedEncodedPathCharacters = *m.DeniedEncodedPathCharacters
|
cp.DeniedEncodedPathCharacters = m.DeniedEncodedPathCharacters
|
||||||
}
|
}
|
||||||
|
|
||||||
rtName := name
|
rtName := name
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,10 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// denyFragment rejects the request if the URL path contains a fragment (hash character).
|
|
||||||
// When go receives an HTTP request, it assumes the absence of fragment URL.
|
|
||||||
// However, it is still possible to send a fragment in the request.
|
|
||||||
// In this case, Traefik will encode the '#' character, altering the request's intended meaning.
|
|
||||||
// To avoid this behavior, the following function rejects requests that include a fragment in the URL.
|
|
||||||
func denyFragment(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
if strings.Contains(req.URL.RawPath, "#") {
|
|
||||||
log.WithoutContext().Debugf("Rejecting request because it contains a fragment in the URL path: %s", req.URL.RawPath)
|
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ServeHTTP(rw, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// denyEncodedPathCharacters reject the request if the escaped path contains encoded characters in the given list.
|
// denyEncodedPathCharacters reject the request if the escaped path contains encoded characters in the given list.
|
||||||
func denyEncodedPathCharacters(encodedCharacters map[string]struct{}, h http.Handler) http.Handler {
|
func denyEncodedPathCharacters(encodedCharacters map[string]struct{}, h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -8,42 +8,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_denyFragment(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
wantStatus int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Rejects fragment character",
|
|
||||||
url: "http://example.com/#",
|
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Allows without fragment",
|
|
||||||
url: "http://example.com/",
|
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
handler := denyFragment(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
|
||||||
res := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(res, req)
|
|
||||||
|
|
||||||
assert.Equal(t, test.wantStatus, res.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_denyEncodedPathCharacters(t *testing.T) {
|
func Test_denyEncodedPathCharacters(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -223,14 +223,11 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
||||||
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
|
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we are adding deny handlers for encoded path characters and fragment.
|
if router.DeniedEncodedPathCharacters != nil {
|
||||||
// Deny handler are only added for root routers, child routers are protected by their parent router deny handlers.
|
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
return denyEncodedPathCharacters(router.DeniedEncodedPathCharacters.Map(), next), nil
|
||||||
return denyFragment(next), nil
|
})
|
||||||
})
|
}
|
||||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
|
||||||
return denyEncodedPathCharacters(router.DeniedEncodedPathCharacters.Map(), next), nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return chain.Extend(*mHandler).Append(tHandler).Then(sHandler)
|
return chain.Extend(*mHandler).Append(tHandler).Then(sHandler)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -910,13 +910,16 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "unallowed request with encoded slash",
|
desc: "disallow request with encoded slash",
|
||||||
requestPath: "/foo%2F",
|
requestPath: "/foo%2F",
|
||||||
routers: map[string]*dynamic.Router{
|
routers: map[string]*dynamic.Router{
|
||||||
"parent": {
|
"parent": {
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
Service: "service",
|
Service: "service",
|
||||||
|
DeniedEncodedPathCharacters: &dynamic.RouterDeniedEncodedPathCharacters{
|
||||||
|
AllowEncodedSlash: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
services: map[string]*dynamic.Service{
|
services: map[string]*dynamic.Service{
|
||||||
|
|
@ -936,9 +939,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
Service: "service",
|
Service: "service",
|
||||||
DeniedEncodedPathCharacters: dynamic.RouterDeniedEncodedPathCharacters{
|
|
||||||
AllowEncodedSlash: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
services: map[string]*dynamic.Service{
|
services: map[string]*dynamic.Service{
|
||||||
|
|
@ -950,25 +950,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusBadGateway,
|
expectedStatusCode: http.StatusBadGateway,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "unallowed request with fragment",
|
|
||||||
requestPath: "/foo#",
|
|
||||||
routers: map[string]*dynamic.Router{
|
|
||||||
"parent": {
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Rule: "PathPrefix(`/`)",
|
|
||||||
Service: "service",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
services: map[string]*dynamic.Service{
|
|
||||||
"service": {
|
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
||||||
Servers: []dynamic.Server{{URL: "http://localhost:8080"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
||||||
|
|
@ -606,6 +606,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
||||||
|
|
||||||
handler = normalizePath(handler)
|
handler = normalizePath(handler)
|
||||||
|
|
||||||
|
handler = denyFragment(handler)
|
||||||
|
|
||||||
serverHTTP := &http.Server{
|
serverHTTP := &http.Server{
|
||||||
Protocols: &protocols,
|
Protocols: &protocols,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
|
|
@ -685,6 +687,24 @@ func (t *trackedConnection) Close() error {
|
||||||
return t.WriteCloser.Close()
|
return t.WriteCloser.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// denyFragment rejects the request if the URL path contains a fragment (hash character).
|
||||||
|
// When go receives an HTTP request, it assumes the absence of fragment URL.
|
||||||
|
// However, it is still possible to send a fragment in the request.
|
||||||
|
// In this case, Traefik will encode the '#' character, altering the request's intended meaning.
|
||||||
|
// To avoid this behavior, the following function rejects requests that include a fragment in the URL.
|
||||||
|
func denyFragment(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if strings.Contains(req.URL.RawPath, "#") {
|
||||||
|
log.WithoutContext().Debugf("Rejecting request because it contains a fragment in the URL path: %s", req.URL.RawPath)
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// This function is inspired by http.AllowQuerySemicolons.
|
// This function is inspired by http.AllowQuerySemicolons.
|
||||||
func encodeQuerySemicolons(h http.Handler) http.Handler {
|
func encodeQuerySemicolons(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,42 @@ func TestKeepAliveH2c(t *testing.T) {
|
||||||
require.Contains(t, err.Error(), "use of closed network connection")
|
require.Contains(t, err.Error(), "use of closed network connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_denyFragment(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
wantStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Rejects fragment character",
|
||||||
|
url: "http://example.com/#",
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Allows without fragment",
|
||||||
|
url: "http://example.com/",
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler := denyFragment(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
assert.Equal(t, test.wantStatus, res.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSanitizePath(t *testing.T) {
|
func TestSanitizePath(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
path string
|
path string
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue