]> git.kaiwu.me - nginx.git/commitdiff
Improved host header validation.
authorSergey Kandaurov <pluknet@nginx.com>
Tue, 4 Nov 2025 12:34:32 +0000 (16:34 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Wed, 26 Nov 2025 15:51:40 +0000 (19:51 +0400)
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

src/http/ngx_http_request.c
src/stream/ngx_stream_core_module.c

index 533af452fca6a94d420ecc9241586f5830d4fcc8..557de66968d7752ba6a818e12876175068bcb708 100644 (file)
@@ -2184,72 +2184,173 @@ ngx_http_process_request(ngx_http_request_t *r)
 ngx_int_t
 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;
+    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 '.':
-            if (dot_pos == i - 1) {
-                return NGX_DECLINED;
-            }
-            dot_pos = i;
-            break;
+        case sw_host_start:
 
-        case ':':
-            if (state == sw_usual) {
-                host_len = i;
-                state = sw_rest;
+            if (ch == '[') {
+                state = sw_host_ip_literal;
+                break;
             }
-            break;
 
-        case '[':
-            if (i == 0) {
-                state = sw_literal;
-            }
-            break;
+            state = sw_host;
 
-        case ']':
-            if (state == sw_literal) {
-                host_len = i + 1;
-                state = sw_rest;
+            /* fall through */
+
+        case sw_host:
+
+            if (ch >= 'A' && ch <= 'Z') {
+                alloc = 1;
+                break;
             }
-            break;
 
-        default:
+            if (ch >= 'a' && ch <= 'z') {
+                break;
+            }
 
-            if (ngx_path_separator(ch)) {
-                return NGX_DECLINED;
+            if (ch >= '0' && ch <= '9') {
+                break;
             }
 
-            if (ch <= 0x20 || ch == 0x7f) {
+            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;
+
+        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) {
         host_len--;
     }
index 40951c2914665a5927458fa277171fdf2d9032a8..a09c7c634f45530c19cd7087f7db7f32cae02469 100644 (file)
@@ -474,72 +474,173 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s,
 ngx_int_t
 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;
+    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 '.':
-            if (dot_pos == i - 1) {
-                return NGX_DECLINED;
-            }
-            dot_pos = i;
-            break;
+        case sw_host_start:
 
-        case ':':
-            if (state == sw_usual) {
-                host_len = i;
-                state = sw_rest;
+            if (ch == '[') {
+                state = sw_host_ip_literal;
+                break;
             }
-            break;
 
-        case '[':
-            if (i == 0) {
-                state = sw_literal;
-            }
-            break;
+            state = sw_host;
 
-        case ']':
-            if (state == sw_literal) {
-                host_len = i + 1;
-                state = sw_rest;
+            /* fall through */
+
+        case sw_host:
+
+            if (ch >= 'A' && ch <= 'Z') {
+                alloc = 1;
+                break;
             }
-            break;
 
-        default:
+            if (ch >= 'a' && ch <= 'z') {
+                break;
+            }
 
-            if (ngx_path_separator(ch)) {
-                return NGX_DECLINED;
+            if (ch >= '0' && ch <= '9') {
+                break;
             }
 
-            if (ch <= 0x20 || ch == 0x7f) {
+            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;
+
+        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) {
         host_len--;
     }