0

I'm trying to use Nginx (1.21.3) rewrite, but somehow it deletes the / key when it's first in string.

rewrite rule:

#nginx not relevant conf here
location / {
    rewrite ^(.*)data/([0-9]+)/(.+)?$ $1processor.php?key=$2&data=$3 last;
}
#nginx not relevant conf here

When I use this rewrite rule for any url I tested, it was okay. When I tried url like the example below, it somehow omitted the / in the beginning.

https://example.com/data/9/%2F*-%2B.%60!%40%23%24%25%5E%26*()_%2B%60-%3D%5B%5D%3B%27%5C%2C.%2F%7B%7D%3A%22%7C%3C%3E%3F

When I reloaded nginx with notices and rewrite_log=on; I got the output:

2021/09/25 13:08:29 [notice] 528#528: *11710 "^(.*)data/([0-9]+)/(.+)?$" matches "/data/199/*-+.`!@#$%^&*()_+`-=[];'\,./{}:"|<>?", client: 192.168.255.107, server: localhost, request: "GET /data/199/%2F%2A-%2B.%60%21%40%23%24%25%5E%26%2A%28%29_%2B%60-%3D%5B%5D%3B%27%5C%2C.%2F%7B%7D%3A%22%7C%3C%3E%3F HTTP/2.0", host: "example.com", referrer: "https://example.com/"

PHP (8.0.10) $_GET["data"] output is (as you can see without / thus not exact math):

*-+.`!@#$%^&*()_+`-=[];'\,./{}:"|<>?

How can I solve it?

Peter Badida
  • 11,310
  • 10
  • 44
  • 90
B. Belete
  • 3
  • 2
  • This seems like a [bug in Nginx](https://stackoverflow.com/a/37584637/8053274). I suggest you pass it as a [base64 encoded string](https://stackoverflow.com/a/42165948/8053274) – User863 Sep 25 '21 at 14:04

1 Answers1

0

Both rewrite and location directives works with so-called normalized URI:

The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.

This means that on the first stage your URL /data/9/%2F*-%2B.%60!%40%23%24%25%5E%26*()_%2B%60-%3D%5B%5D%3B%27%5C%2C.%2F%7B%7D%3A%22%7C%3C%3E%3F gets URL-decoded:

/data/9//*-+.`!@#$%^&*()_+`-=[];'\,./{}:"|<>?

and on the second stage two adjacent slashes getting compressed into the one:

/data/9/*-+.`!@#$%^&*()_+`-=[];'\,./{}:"|<>?

Exactly the above string is subject to test for rewrite directive thus leading to missing of the first URL-encoded slash. However you can use the $request_uri variable which contains the request URI in a non-modified form. You can use either

if ($request_uri ~ ^(?<prefix>.*/)data/(?<key>\d+)/(?<data>[^?]+)) {
    rewrite ^ ${prefix}processor.php?key=$key&data=$data;
}

block to be placed under server context or

location /
    if ($request_uri ~ ^(?<prefix>.*/)data/(?<key>\d+)/(?<data>[^?]+)) {
        rewrite ^ ${prefix}processor.php?key=$key&data=$data last;
    }
    ...
}

block placed under location context.

Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37
  • Thanks you so much Ivan, what you think about using the merge_slashes = off? – B. Belete Sep 25 '21 at 15:36
  • I'd rather not. It should work too, but there can be some [caveats](https://nginx.org/en/docs/http/ngx_http_core_module.html#merge_slashes): _Note that compression is essential for the correct matching of prefix string and regular expression locations. Without it, the `//scripts/one.php` request would not match `location /scripts/ { ... }` and might be processed as a static file. So it gets converted to `/scripts/one.php`._ – Ivan Shatsky Sep 25 '21 at 15:42