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

@ -2186,68 +2186,169 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{
u_char *h, ch;
size_t i, dot_pos, host_len;
ngx_int_t port;
enum {
sw_usual = 0,
sw_literal,
sw_rest
sw_host_start = 0,
sw_host,
sw_host_ip_literal,
sw_host_end,
sw_port,
} state;
dot_pos = host->len;
host_len = host->len;
port = 0;
h = host->data;
state = sw_usual;
state = sw_host_start;
for (i = 0; i < host->len; i++) {
ch = h[i];
switch (ch) {
switch (state) {
case sw_host_start:
if (ch == '[') {
state = sw_host_ip_literal;
break;
}
state = sw_host;
/* fall through */
case sw_host:
if (ch >= 'A' && ch <= 'Z') {
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 ':':
if (state == sw_usual) {
host_len = i;
state = sw_rest;
}
case '_':
case '~':
/* unreserved */
break;
case '[':
if (i == 0) {
state = sw_literal;
}
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
case ']':
if (state == sw_literal) {
host_len = i + 1;
state = sw_rest;
}
case '%':
/* pct-encoded */
break;
default:
if (ngx_path_separator(ch)) {
return NGX_DECLINED;
}
break;
if (ch <= 0x20 || ch == 0x7f) {
return NGX_DECLINED;
}
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) {

View File

@ -476,68 +476,169 @@ ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{
u_char *h, ch;
size_t i, dot_pos, host_len;
ngx_int_t port;
enum {
sw_usual = 0,
sw_literal,
sw_rest
sw_host_start = 0,
sw_host,
sw_host_ip_literal,
sw_host_end,
sw_port,
} state;
dot_pos = host->len;
host_len = host->len;
port = 0;
h = host->data;
state = sw_usual;
state = sw_host_start;
for (i = 0; i < host->len; i++) {
ch = h[i];
switch (ch) {
switch (state) {
case sw_host_start:
if (ch == '[') {
state = sw_host_ip_literal;
break;
}
state = sw_host;
/* fall through */
case sw_host:
if (ch >= 'A' && ch <= 'Z') {
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 ':':
if (state == sw_usual) {
host_len = i;
state = sw_rest;
}
case '_':
case '~':
/* unreserved */
break;
case '[':
if (i == 0) {
state = sw_literal;
}
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
case ']':
if (state == sw_literal) {
host_len = i + 1;
state = sw_rest;
}
case '%':
/* pct-encoded */
break;
default:
if (ngx_path_separator(ch)) {
return NGX_DECLINED;
}
break;
if (ch <= 0x20 || ch == 0x7f) {
return NGX_DECLINED;
}
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) {