nginx - deny all *.php requests except index.php for security reasons

10,264

You can achieve this in a number of ways.

Integrating quite directly with what you have in your config file, you may wish to simply include a section such as the following;

location ~ \.php$ {
try_files index.php @error;

fastcgi_pass ...;

fastcgi_param SCRIPT_FILENAME /path/to$fastcgi_script_name;

...
}

location @error {
[config of however you want to handle errors]
}

Which will check for the existence of the requested file before allowing its access/execution.

Further to the above however, I would actually personally recommend using fail2ban which will provide you more comprehensive security if configured correctly; you can configure it to monitor your access logs in real-time and ban IPs from accessing your server(s) by automatically creating new iptables rules on-the-fly, with ban times which you specify.

Personally I have my servers configured to use fail2ban with nginx as per this article (or at least based upon that - you may alter it as you wish).

Share:
10,264

Related videos on Youtube

Sergey Serov
Author by

Sergey Serov

Programmer, Social Anthropologist. Languages: PHP, Python, JavaScript. Databases: MySQL, MariaDB. Linux: CentOS, Fedora, Ubuntu. Front-end: HTML, CSS. CMS and Frameworks: Drupal, jQuery. Version Control: git. My modules on drupal.org: https://drupal.org/project/popup_announcement https://drupal.org/project/google_recaptcha https://www.drupal.org/project/editor_email_link Personal site: www.sergey-serov.ru

Updated on September 18, 2022

Comments

  • Sergey Serov
    Sergey Serov over 1 year

    os: CentOS 7
    nginx: 1.6.2
    httpd: apache 2.4.6
    cms: Drupal 7

    After my server was compromised I removed all from server, reinstalled OS and soft, and restored data from backup. Now I configure all services in maximum security style.

    After detail researching access logs - I decided to deny any requests for php files except index.php which is in the site documents root for improving security.

    Nginx access log contents a lot of records like:

    azenv2.php
    az.php
    

    and

    /*/wp-login.php
    /administrator/index.php
    /MyAdmin/index.php
    

    First category - backdoors (and one of them hacked my sites, somebody send huge portion of spam from my server).

    Second - somebody want to find popular cms and utilities and try some login@password, like admin@123456

    My reasons to block both categories by nginx through deny requests to php files are:

    1. Even if somebody will upload php-shell - it will be impossible to use it.

    2. All these requests are 'not good' a priory - and to refuse them by nginx will protect drupal(httpd+php+mysql) to work and spent power.

    My current config for one virtual host:

    server {
    
      listen <server-ip>;
      server_name <site-name>;
    
      location ~* /sites/default/files/styles/ {
        try_files $uri @imagestyles;
      }
    
      location @imagestyles {
        proxy_pass http://127.0.0.1:<port>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        access_log off;
      }
    
      location ~* \.(jpg|jpeg|gif|png|ico|css|bmp|swf|js|pdf|zip|rar|mp3|flv|doc|xls)$ {
        root <site-documents-root>;
        access_log off;
      }
    
      location ~ (^|/)\. {
        deny  all;
      }
    
      location / {
        proxy_pass http://127.0.0.1:<port>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        access_log <path-to-log-folder>/nginx_access.log main;
      }
    
    }
    

    nginx.conf - was not changed after installation.


    UPDATE
    Finally I create this config for deny:

    location ~ \.php$ {
      access_log /path/to/log/nginx_deny.log name_log;
      deny all;
    }
    

    and this config for proxy:

    location =/index.php {
      proxy_pass http://127.0.0.1:<port>;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
    
    location =/cron.php {
      proxy_pass http://127.0.0.1:<port>;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
    
    location / {
      proxy_pass http://127.0.0.1:<port>;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
    

    1). So full information about attacks attempts is collects in log.
    2). Server not make additional work for bad requests.
    3). Drupal cron may work.

    • Deer Hunter
      Deer Hunter over 9 years
      A probably superfluous question: have you read the canonical serverfault.com/questions/218005/…
    • Sergey Serov
      Sergey Serov over 9 years
      I had read a lot of about security during last two weeks :) I have no problems with fact of compromised - all actions already done - new os, soft, complex clean sites files. Now - I need to protect by maximum new server. ssh, selinux, httpd, php seems secure. Strongly nginx configurations can improve security.
    • BE77Y
      BE77Y over 9 years
      @Peter unfortunately that would result in the denial of the request to index.php also, as the location ~\.php$ { deny all } directive would also match index.php
    • AD7six
      AD7six over 9 years
      What exactly does which is in the site documents root for improving security mean? Where was it before? There's also no php handling at all in the above config - so not very clear.
    • Sergey Serov
      Sergey Serov over 9 years
      AD7six, index.php always in the site root as by default. I want to refuse requests to use backdors (if they will appear in future) and deny spam traffic. I added some more information to the main description.
  • BE77Y
    BE77Y over 9 years
    Of course not, but that is neither what was requested by OP nor what I detailed in my response. It is however a point worth noting - known vulnerabilities in packages should be tracked and software updated accordingly.
  • BE77Y
    BE77Y over 9 years
    Alright, it would seem that try_files requires at least two arguments then - I would suggest that you set up a second argument to point to an error location if you wish (answer updated to reflect this change above)