]> git.kaiwu.me - njs.git/commitdiff
Fetch: reject unsafe request targets
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 29 May 2026 23:04:56 +0000 (16:04 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 1 Jun 2026 15:44:59 +0000 (08:44 -0700)
Reject raw C0 control bytes, space, and DEL in the parsed request target
before fetch request serialization.  Leave percent-encoded bytes unchanged.

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

index 10219545f71b45c560de607dc27443e3f30bf16b..b1bf55eca854c15924c3fce1fa0a5646bf39ac25 100644 (file)
@@ -627,6 +627,11 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         resolve_host = &u.host;
     }
 
+    if (ngx_js_check_request_line_component(u.uri.data, u.uri.len) != NGX_OK) {
+        njs_vm_error(vm, "invalid url");
+        goto fail;
+    }
+
     ngx_js_fetch_build_request(http, &request, &u.uri, &u,
                                ngx_js_http_proxy(http) && !ngx_js_https(&u));
 
index 3a41a78c22d8468504bf9f61bee6c97382bd210e..ec7ac08af53337c5f11e42cd01eb65eafb7df4b3 100644 (file)
@@ -364,6 +364,11 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc,
         resolve_host = &u.host;
     }
 
+    if (ngx_js_check_request_line_component(u.uri.data, u.uri.len) != NGX_OK) {
+        JS_ThrowInternalError(cx, "invalid url");
+        goto fail;
+    }
+
     ngx_js_fetch_build_request(http, &request, &u.uri, &u,
                                ngx_js_http_proxy(http) && !ngx_js_https(&u));
 
index 1bc0ef9396dff4ef135ea4a53d12fce6af283b4f..8bf9cf1de6d9f097abefb219c3a2513361c453af 100644 (file)
@@ -73,6 +73,10 @@ http {
             return 200 $http_a;
         }
 
+        location /target {
+            return 200 $request_uri;
+        }
+
         location /body {
             js_content test.body;
         }
@@ -566,6 +570,30 @@ $t->write_file('test.js', <<EOF);
                 var body = await r.text();
                 return `\${r.url}: \${r.status} \${body}`;
              }, 'http://127.0.0.1:$p0/body: 201 foo'],
+            ['unsafe request target', async () => {
+                const unsafe = [0, 9, 10, 13, 32, 127];
+
+                for (var i = 0; i < unsafe.length; i++) {
+                    try {
+                        await ngx.fetch('http://127.0.0.1:$p0/a'
+                                        + String.fromCharCode(unsafe[i]) + 'b');
+                        throw new Error('no error');
+
+                    } catch (e) {
+                        if (e.message != 'invalid url') {
+                            throw e;
+                        }
+                    }
+                }
+
+                return 'OK';
+             }, 'OK'],
+            ['encoded request target', async () => {
+                var r = await ngx.fetch('http://127.0.0.1:$p0/'
+                                        + 'target%0D%0A?q=%00%20');
+                var body = await r.text();
+                return `\${r.status} \${body}`;
+             }, '200 /target%0D%0A?q=%00%20'],
         ];
 
         run(r, tests);