Improved host header validation.

Validation is rewritten to follow RFC 3986 host syntax, based on
ngx_http_parse_request_line().  The following is now rejected:
- the rest of gen-delims "#", "?", "@", "[", "]"
- other unwise delims <">, "<", ">", "\", "^", "`', "{", "|", "}"
- IP literals with a trailing dot, missing closing bracket, or pct-encoded
- a port subcomponent with invalid values
- characters in upper half
This commit is contained in:
Sergey Kandaurov 2025-11-04 16:34:32 +04:00 committed by Roman Arutyunyan
parent 6ed1188411
commit 511abb19e1
2 changed files with 282 additions and 80 deletions

View File

@ -2184,72 +2184,173 @@ ngx_http_process_request(ngx_http_request_t *r)
ngx_int_t ngx_int_t
ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{ {
u_char *h, ch; u_char *h, ch;
size_t i, dot_pos, host_len; size_t i, dot_pos, host_len;
ngx_int_t port;
enum { enum {
sw_usual = 0, sw_host_start = 0,
sw_literal, sw_host,
sw_rest sw_host_ip_literal,
sw_host_end,
sw_port,
} state; } state;
dot_pos = host->len; dot_pos = host->len;
host_len = host->len; host_len = host->len;
port = 0;
h = host->data; h = host->data;
state = sw_usual; state = sw_host_start;
for (i = 0; i < host->len; i++) { for (i = 0; i < host->len; i++) {
ch = h[i]; ch = h[i];
switch (ch) { switch (state) {
case '.': case sw_host_start:
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case ':': if (ch == '[') {
if (state == sw_usual) { state = sw_host_ip_literal;
host_len = i; break;
state = sw_rest;
}
break;
case '[':
if (i == 0) {
state = sw_literal;
}
break;
case ']':
if (state == sw_literal) {
host_len = i + 1;
state = sw_rest;
}
break;
default:
if (ngx_path_separator(ch)) {
return NGX_DECLINED;
} }
if (ch <= 0x20 || ch == 0x7f) { state = sw_host;
return NGX_DECLINED;
} /* fall through */
case sw_host:
if (ch >= 'A' && ch <= 'Z') { if (ch >= 'A' && ch <= 'Z') {
alloc = 1; alloc = 1;
break;
} }
if (ch >= 'a' && ch <= 'z') {
break;
}
if (ch >= '0' && ch <= '9') {
break;
}
switch (ch) {
case ':':
host_len = i;
state = sw_port;
break;
case '-':
break;
case '.':
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case '_':
case '~':
/* unreserved */
break;
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
case '%':
/* pct-encoded */
break;
default:
return NGX_DECLINED;
}
break; break;
case sw_host_ip_literal:
if (ch >= 'A' && ch <= 'Z') {
alloc = 1;
break;
}
if (ch >= 'a' && ch <= 'z') {
break;
}
if (ch >= '0' && ch <= '9') {
break;
}
switch (ch) {
case ':':
break;
case ']':
host_len = i + 1;
state = sw_host_end;
break;
case '-':
break;
case '.':
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case '_':
case '~':
/* unreserved */
break;
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
default:
return NGX_DECLINED;
}
break;
case sw_host_end:
if (ch == ':') {
state = sw_port;
break;
}
return NGX_DECLINED;
case sw_port:
if (ch >= '0' && ch <= '9') {
if (port >= 6553 && (port > 6553 || (ch - '0') > 5)) {
return NGX_DECLINED;
}
port = port * 10 + (ch - '0');
break;
}
return NGX_DECLINED;
} }
} }
if (state == sw_host_ip_literal) {
return NGX_DECLINED;
}
if (dot_pos == host_len - 1) { if (dot_pos == host_len - 1) {
host_len--; host_len--;
} }

View File

@ -474,72 +474,173 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s,
ngx_int_t ngx_int_t
ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{ {
u_char *h, ch; u_char *h, ch;
size_t i, dot_pos, host_len; size_t i, dot_pos, host_len;
ngx_int_t port;
enum { enum {
sw_usual = 0, sw_host_start = 0,
sw_literal, sw_host,
sw_rest sw_host_ip_literal,
sw_host_end,
sw_port,
} state; } state;
dot_pos = host->len; dot_pos = host->len;
host_len = host->len; host_len = host->len;
port = 0;
h = host->data; h = host->data;
state = sw_usual; state = sw_host_start;
for (i = 0; i < host->len; i++) { for (i = 0; i < host->len; i++) {
ch = h[i]; ch = h[i];
switch (ch) { switch (state) {
case '.': case sw_host_start:
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case ':': if (ch == '[') {
if (state == sw_usual) { state = sw_host_ip_literal;
host_len = i; break;
state = sw_rest;
}
break;
case '[':
if (i == 0) {
state = sw_literal;
}
break;
case ']':
if (state == sw_literal) {
host_len = i + 1;
state = sw_rest;
}
break;
default:
if (ngx_path_separator(ch)) {
return NGX_DECLINED;
} }
if (ch <= 0x20 || ch == 0x7f) { state = sw_host;
return NGX_DECLINED;
} /* fall through */
case sw_host:
if (ch >= 'A' && ch <= 'Z') { if (ch >= 'A' && ch <= 'Z') {
alloc = 1; alloc = 1;
break;
} }
if (ch >= 'a' && ch <= 'z') {
break;
}
if (ch >= '0' && ch <= '9') {
break;
}
switch (ch) {
case ':':
host_len = i;
state = sw_port;
break;
case '-':
break;
case '.':
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case '_':
case '~':
/* unreserved */
break;
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
case '%':
/* pct-encoded */
break;
default:
return NGX_DECLINED;
}
break; break;
case sw_host_ip_literal:
if (ch >= 'A' && ch <= 'Z') {
alloc = 1;
break;
}
if (ch >= 'a' && ch <= 'z') {
break;
}
if (ch >= '0' && ch <= '9') {
break;
}
switch (ch) {
case ':':
break;
case ']':
host_len = i + 1;
state = sw_host_end;
break;
case '-':
break;
case '.':
if (dot_pos == i - 1) {
return NGX_DECLINED;
}
dot_pos = i;
break;
case '_':
case '~':
/* unreserved */
break;
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
default:
return NGX_DECLINED;
}
break;
case sw_host_end:
if (ch == ':') {
state = sw_port;
break;
}
return NGX_DECLINED;
case sw_port:
if (ch >= '0' && ch <= '9') {
if (port >= 6553 && (port > 6553 || (ch - '0') > 5)) {
return NGX_DECLINED;
}
port = port * 10 + (ch - '0');
break;
}
return NGX_DECLINED;
} }
} }
if (state == sw_host_ip_literal) {
return NGX_DECLINED;
}
if (dot_pos == host_len - 1) { if (dot_pos == host_len - 1) {
host_len--; host_len--;
} }