owasp-modsecurity / ModSecurity-nginx

ModSecurity v3 Nginx Connector
Apache License 2.0
1.59k stars 282 forks source link

REMOTE_ADDR behind a reverse proxy - modsec doesn't see fastcgi_params adjustments. #195

Closed gridpane closed 4 years ago

gridpane commented 4 years ago

We are running ModSec behind an Nginx Reverse Proxy, but not Nginx -> Apache.

We have Nginx up front as a caching reverse proxy, and we are passing back to an Nginx FastCGI/PHP-FPM Backend.

ModSec sits on the back, guarding the dynamic requests only. In general its turned out to be an excellent solution, inspired from Trustwaves performance recommendations but without needing to involve anything else in the stack, rather its just an interesting Nginx configuration. https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/modsecurity-performance-recommendations/

Any cached requests fly, with only dynamic requests suffering from the ModSec weight.

However... the inability to access and rewrite REMOTE_ADDR in the back is problematic.

Basic config:

upstream proxy {
     server 127.0.0.1:8080;
}

server {
    listen 443 ssl http2};
    listen [::]:443 ssl http2;

    server_name site.url;

    ... skip cache whatever else here ...

    location / {
        proxy_cache_bypass $skip_cache;
        proxy_no_cache $skip_cache;
        proxy_cache PROXYCACHE;
        more_set_headers 'X-Grid-Cache-TTL $cache_ttl';
        more_set_headers 'X-Grid-Cache-Skip $skip_reason';
        more_set_headers 'X-Grid-Cache $upstream_cache_status';
        set $http_x_accel_expires $cache_ttl;
        proxy_hide_header X-Powered-By;
        proxy_set_header Host $host;
        proxy_set_header X-Original-Host $http_host;
        proxy_set_header X-Original-Scheme $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://proxy;
    }
}

server {
    listen 8080;
    listen [::]:8080;

    server_name site.url;

    allow 127.0.0.1;
    deny all;

    ... root, logs, index, whatever ...

    modsecurity on;
    port_in_redirect off;

    location / {
        modsecurity_rules_file /etc/nginx/modsec/main.conf;
         try_files $uri $uri/ /index.php?$args;
         ... etc ...
    }

    location ~ \.php$ {
        modsecurity_rules_file /etc/nginx/modsec/main.conf;
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(.*)$;
        include fastcgi_params;
        include /etc/nginx/extra.d/php-context.conf;
        include /etc/nginx/extra.d/*-php-context.conf;
        fastcgi_param HTTPS on;
        fastcgi_pass unix:/var/run/php/php73-site.url.sock
        http2_push_preload on;
    }
}

So the problem is that when an event happens, its logged as the localhost 127.0.0.1

This is not so much of a big deal, as i can target rule exceptions using the ID and things like headers, or setting cookie values:

SecRule REQUEST_HEADERS:X-Real-IP "@pm 192.3.123.56 204.44.111.4" \
   "id:3,\
    phase:2,\
    nolog,\
    allow,\
    pass,\
    ctl:ruleRemoveById=949110"

But... modsec sees everything as 127.0.0.1 - this is the source IP - the REMOTE_ADDR.

Now I can rewrite this for PHP functions quite easily using the fastcgi_param directive. I can set this in values up front even and pass back through, variety of ways one for example:

fastcgi_param REMOTE_ADDR $realip_remote_addr;

So using a test.php file

<?php var_export($_SERVER)?>

I can output things and see that as far as PHP processing is concerned I have managed to rewrite the REMOTE_ADDR value back to the real IP... however doing this with fastcgi_params is all post Modsec.

It seems ModSec runs phase earlier. Is there anyway to rewrite this prior to ModSec accessing the value?

The real issue is having to set the DOS functions to deal with the fact that ModSec sees every single request as coming from the same IP.

Am I missing something, is there a reason that ModSec can't access these values after fastcgi_params has adjusted them?

martinhsv commented 4 years ago

Hello @gridpane ,

One thing to note is that the fastcgi_param called REMOTE_ADDR is not the same as the ModSecurity variable REMOTE_ADDR. They happen to have the same name but they don't refer to the same set of bytes, nor is one sourced from the other.

I don't believe there is any way to reassign the value of ModSecurity's REMOTE_ADDR.

What you can do is, if the real ip is supplied in a request header, set a custom variable to hold that value and then do any ip-address-related rule functionality using that variable.

There's a slightly longer discussion of the general idea in https://github.com/SpiderLabs/ModSecurity/issues/1620

gridpane commented 4 years ago

Thanks Martin