mirror of
https://github.com/nginx/nginx.git
synced 2026-05-27 11:53:27 -04:00
Proxy: escape ';' in URI paths
This fixes a security hole when used with many Java application servers,
which treat ';' as a special character in paths. See [1] for more
details.
This changes how NGINX constructs requests for its backends. Something
like:
location /fake1/ {
proxy_pass http://127.0.0.1:8000/fake2/;
}
will now escape semicolons in the provided URL, provided that the
original URL had any %-escapes.
To prevent such bypasses, configurations can use:
if ($request_uri !~ "^/[^;?#]*(?:\?|$)") {
return 400 "Path parameters not allowed";
}
This is ultimately a flaw in NGINX, not the backend servers: it is a
client error to make an HTTP request with a URI that contains a path
with a character that is not valid in paths, and as the client NGINX is
responsible for only making valid requests.
This is not a complete fix. A complete fix would require blocking %2F
in paths (HAProxy and NGINX disagree on its meaning) and blocking
unescaped semicolons in paths (NGINX and Java app servers disagree on
their meaning).
This patch allows closing the remaining bypasses by doing the following
in configuration files:
1. Do not use regular expressions in paths unless careful.
2. Ensure all location blocks are either "location = /something" (note
the = operator) or location /something/ (trailing slash).
3. Include the following at server level:
if ($request_uri !~ "^(?<non_normalized_path>/[^;?#]*)(?:\?|$)") {
return 400 "Path parameters not allowed";
}
if ($non_normalized_path ~ "%2[EeFf]|//|/\.\.?(?:\?|/|$)") {
return 400 "Path has escaped . or /, repeated /, or . or .. component";
}
This checks that path components are not empty and are not "." or "..".
It also checks that there are no path parameters and no escaped "." or
"/" characters. Paths that meet these requirements will be interpreted
consistently by all web servers I am aware of.
One can consider this snippet to be licensed under the MIT-0 license (no
attribution required, use for any purpose allowed with no restrictions).
Without this patch to NGINX, normalized URLs submitted to backends would
have unescaped semicolons even if the URL submitted to NGINX escaped the
semicolon. Preventing the bypass would therefore require blocking
*escaped* semicolons, which should not be required.
[1]: https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf
This commit is contained in:
parent
d44205284f
commit
5e297f39ee
4 changed files with 29 additions and 9 deletions
|
|
@ -1654,13 +1654,32 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type)
|
|||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* " ", "#", "%", ";", "?", not allowed */
|
||||
|
||||
static uint32_t uri_path[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0xd800002d, /* 1101 1000 0000 0000 0000 0000 0010 1101 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
static uint32_t *map[] =
|
||||
{ uri, args, uri_component, html, refresh, memcached, memcached,
|
||||
mail_xtext };
|
||||
mail_xtext, uri_path };
|
||||
|
||||
static u_char map_char[] =
|
||||
{ '%', '%', '%', '%', '%', '%', '%', '+' };
|
||||
|
||||
{ '%', '%', '%', '%', '%', '%', '%', '+', '%' };
|
||||
|
||||
escape = map[type];
|
||||
prefix = map_char[type];
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);
|
|||
#define NGX_ESCAPE_MEMCACHED 5
|
||||
#define NGX_ESCAPE_MAIL_AUTH 6
|
||||
#define NGX_ESCAPE_MAIL_XTEXT 7
|
||||
#define NGX_ESCAPE_URI_PATH 8
|
||||
|
||||
#define NGX_UNESCAPE_URI 1
|
||||
#define NGX_UNESCAPE_REDIRECT 2
|
||||
|
|
|
|||
|
|
@ -1130,7 +1130,7 @@ ngx_http_proxy_create_key(ngx_http_request_t *r)
|
|||
|
||||
if (r->quoted_uri || r->internal) {
|
||||
escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
} else {
|
||||
escape = 0;
|
||||
}
|
||||
|
|
@ -1151,7 +1151,7 @@ ngx_http_proxy_create_key(ngx_http_request_t *r)
|
|||
|
||||
if (escape) {
|
||||
ngx_escape_uri(p, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
p += r->uri.len - loc_len + escape;
|
||||
|
||||
} else {
|
||||
|
|
@ -1243,7 +1243,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
|
|||
|
||||
if (r->quoted_uri || r->internal) {
|
||||
escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
}
|
||||
|
||||
uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape
|
||||
|
|
@ -1368,7 +1368,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
|
|||
|
||||
if (escape) {
|
||||
ngx_escape_uri(b->last, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
b->last += r->uri.len - loc_len + escape;
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
|
|||
|
||||
if (r->quoted_uri || r->internal) {
|
||||
escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
}
|
||||
|
||||
uri_len = ctx->ctx.vars.uri.len + r->uri.len - loc_len + escape
|
||||
|
|
@ -647,7 +647,7 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
|
|||
|
||||
if (escape) {
|
||||
ngx_escape_uri(p, r->uri.data + loc_len,
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI);
|
||||
r->uri.len - loc_len, NGX_ESCAPE_URI_PATH);
|
||||
p += r->uri.len - loc_len + escape;
|
||||
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue