]> git.kaiwu.me - njs.git/commitdiff
Fetch: reject unsafe request methods
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 29 May 2026 22:58:34 +0000 (15:58 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 1 Jun 2026 15:44:59 +0000 (08:44 -0700)
Reject empty methods, C0 control bytes, space, and DEL before a fetch
request can be serialized.  Preserve existing forbidden-method checks,
common-method normalization, and valid server-side extension methods.

nginx/ngx_js_fetch.c
nginx/ngx_js_http.c
nginx/ngx_js_http.h
nginx/ngx_qjs_fetch.c
nginx/t/js_fetch_objects.t

index ab1ea0c5e658bd148ddbf94cde7f2202771cd273..10219545f71b45c560de607dc27443e3f30bf16b 100644 (file)
@@ -898,6 +898,15 @@ ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request)
     str.start = request->method.data;
     str.length = request->method.len;
 
+    if (request->method.len == 0
+        || ngx_js_check_request_line_component(request->method.data,
+                                               request->method.len)
+           != NGX_OK)
+    {
+        njs_vm_error(vm, "invalid Request method");
+        return NJS_ERROR;
+    }
+
     for (m = &forbidden[0]; m->length != 0; m++) {
         if (njs_strstr_case_eq(&str, m)) {
             njs_vm_error(vm, "forbidden method: %V", m);
index 28084a58a599cb4038fcbaf835e54d555504f531..2df784fe2711f8926b8817f3b301d8164decea9f 100644 (file)
@@ -1768,6 +1768,26 @@ ngx_js_check_header_name(u_char *name, size_t len)
 }
 
 
+ngx_int_t
+ngx_js_check_request_line_component(u_char *value, size_t len)
+{
+    u_char  *p, *end;
+
+    p = value;
+    end = p + len;
+
+    while (p < end) {
+        if (*p <= 0x20 || *p == 0x7f) {
+            return NGX_ERROR;
+        }
+
+        p++;
+    }
+
+    return NGX_OK;
+}
+
+
 ngx_int_t
 ngx_js_check_header_value(u_char *value, size_t len)
 {
index 0e88b68a5cb52a611505e9da8c1392f3ce70556e..b9e4666c469bb5e64ad462efa3822dab5c1b003e 100644 (file)
@@ -182,6 +182,7 @@ void ngx_js_http_trim(u_char **value, size_t *len,
     int trim_c0_control_or_space);
 void ngx_js_http_trim_ows(u_char **value, size_t *len);
 ngx_int_t ngx_js_check_header_name(u_char *name, size_t len);
+ngx_int_t ngx_js_check_request_line_component(u_char *value, size_t len);
 ngx_int_t ngx_js_check_header_value(u_char *value, size_t len);
 
 ngx_buf_t *ngx_js_chain_to_buf(ngx_pool_t *pool, njs_chb_t *chain);
index d63e98cbe0009573b81a13bf91c679d0a181755b..3a41a78c22d8468504bf9f61bee6c97382bd210e 100644 (file)
@@ -938,6 +938,15 @@ ngx_qjs_method_process(JSContext *cx, ngx_js_request_t *request)
         ngx_null_string,
     };
 
+    if (request->method.len == 0
+        || ngx_js_check_request_line_component(request->method.data,
+                                               request->method.len)
+           != NGX_OK)
+    {
+        JS_ThrowInternalError(cx, "invalid Request method");
+        return NGX_ERROR;
+    }
+
     for (m = &forbidden[0]; m->len != 0; m++) {
         if (request->method.len == m->len
             && ngx_strncasecmp(request->method.data, m->data, m->len) == 0)
index 0f2fec08cb0fe06cda4b6841a2611af12552a3af..1bc0ef9396dff4ef135ea4a53d12fce6af283b4f 100644 (file)
@@ -349,20 +349,53 @@ $t->write_file('test.js', <<EOF);
              }, 'OK'],
             ['method', () => {
                 const methods = ['get', 'hEad', 'Post', 'OPTIONS', 'PUT',
-                                 'DELETE', 'CONNECT'];
-                try {
-                    methods.forEach(m => {
-                        var r = new Request("http://nginx.org", {method: m});
-                        if (r.method != m.toUpperCase()) {
-                            throw new Error(`r.method != \${m}`);
+                                 'DELETE'];
+                const forbidden = ['CONNECT', 'TRACE', 'TRACK'];
+                const invalid = ['', 'GET ', ' GET', 'GE T', 'GE\\tT',
+                                 'GE\\nT', 'GE\\rT', 'GE\\x00T',
+                                 'GET\\r\\nX: y',
+                                 'GE' + String.fromCharCode(0x7f) + 'T'];
+                const high = 'G' + String.fromCharCode(0xc9) + 'T';
+                const extensions = ['PROPFIND', 'propfind', 'M-SEARCH',
+                                    high, 'FOO/BAR'];
+
+                methods.forEach(m => {
+                    var r = new Request("http://nginx.org", {method: m});
+                    if (r.method != m.toUpperCase()) {
+                        throw new Error(`r.method != \${m}`);
+                    }
+                });
+
+                forbidden.forEach(m => {
+                    try {
+                        new Request("http://nginx.org", {method: m});
+                        throw new Error('no error');
+
+                    } catch (e) {
+                        if (!e.message.startsWith(`forbidden method: \${m}`)) {
+                            throw e;
                         }
-                    })
+                    }
+                });
 
-                } catch (e) {
-                    if (!e.message.startsWith('forbidden method: CONNECT')) {
-                        throw e;
+                invalid.forEach(m => {
+                    try {
+                        new Request("http://nginx.org", {method: m});
+                        throw new Error('no error');
+
+                    } catch (e) {
+                        if (e.message != 'invalid Request method') {
+                            throw e;
+                        }
                     }
-                }
+                });
+
+                extensions.forEach(m => {
+                    var r = new Request("http://nginx.org", {method: m});
+                    if (r.method != m) {
+                        throw new Error(`r.method != \${m}`);
+                    }
+                });
 
                 return 'OK';