nginx / njs

A subset of JavaScript language to use in nginx
http://nginx.org/en/docs/njs/
BSD 2-Clause "Simplified" License
1.15k stars 150 forks source link

`js_content` before proxy_pass is not executed and thus cannot filter malicious requests #799

Open dilyanpalauzov opened 1 week ago

dilyanpalauzov commented 1 week ago

I want to mitigate a DOS attack by filtering incoming requests based on some criteria. If the criteria are met, then the request processing should continue (POST should be passed to proxy_pass). If the criteria are not met, then I can return “200 Hello World!”, redirect to another page, or somehow avoid further handling. My current configuration does use proxy_pass and works very good, except it cannot filter requests based on the desired criteria.

Is it possible to inspect the body of a HTTP request, and if it is OK, then to process the command in the Nginx configuration just after the NJS checks were done (thus handle the lines after js_content, like proxy_pass) ? Should the inspection be done in js_content and what shall be executed in the script: to continue with the next directive in the nginx configuration, or to abort with “200 Hello World!”?

If this is not possible, how should I handle this use case? I had

location / {
   proxy_pass http://127.0.0.2/cgi-bin/wa;
}

and this works very good. Now I do instead a.js:

function filt(r) {
  r.internalRedirect('/b')
}
export default {filt};

nginx config

location / {
  js_content a.filt;
}

location /b {
  internal;
  proxy_pass http://127.0.0.2/cgi-bin/z;
}

If I call directly curl -D- http://127.0.0.2/cgi-bin/z?INDEX there is no location header, but if I run the call over Nginx there is a 302 Location: https://server/b?INDEX header. In the general case, the cgi-bin file will sometimes return and sometimes not return a location header. As /b is internal location, I could rewrite it with proxy_redirect http://127.0.0.2/cgi-bin/z /; and then the reply to the client contains: 302 location: https://server/?INDEX. This leads to a loop, as reply always has 302 location, the request goes again over "location /", then internalRediect('/b') and so on. Removing internal does not help.

For me it seems impossible, when Nginx always includes Location header for proxy_pass after a (possibly internal) redirect within Nginx, to avoid sending 302 Location to the client. This locations cause the a.filt to be executed, in order to filter out the DOS attack, doing internal redirect,

If I put in the same location / { js_content …; and proxy_pass …; } then js_content is not executed (even if it contains a single .log() function) and there is no warnings or hints for this by nginx -t. I have nginx 1.26.2 with njs 0.8.6.

xeioex commented 6 days ago

Hi @dilyanpalauzov,

If I put in the same location / { js_content …; and proxy_pass …; } then js_content is not executed (even if it contains a single .log() function) and there is no warnings or hints for this by nginx -t. I have nginx 1.26.2 with njs 0.8.6.

yes, both js_content and proxy_pass install a content handler for request processing for a given location. nginx allows you to have only have a single content handler for a given location.

For DOS protection, have you considered limit_req and limit_conn modules? It is a recommended and effective approach to mitigate DOS attacks.

dilyanpalauzov commented 6 days ago

Hello,

in my case I do not want to limit somehow the number of requests, I avoid them 100%. limit_req and limit_conn will not help here. What I did at the end was to modify the code of the webserver, which receives the requests from nginx and executes cgi-bin/z.

It would be better, if Nginx/njs had this option to limit traffic.

Greetings // Dilyan