A
A
Amoled2017-10-20 04:52:45
PHP
Amoled, 2017-10-20 04:52:45

What security holes can be in the nginx configuration?

I am doing here for the first time a complete configuration on nginx without apache. Worried if he missed any subtleties that could lead to a security hole. thanks everyone for the comments.
The logic is as follows:
1. Permanent redirect from / to index.php
2. Disable any static except gif|jpg|png|js|css|ttf|woff|ico
3. Allow access only to index.php for everyone, to json.php for the 1st IP, other PHP files should neither be opened nor executed (404), for /secure access is also preventively blocked (just in case).
4. We allow access to /admin only from the 1st IP, for /admin/phpmyadmin the same access (but the CSP changes), and in /admin/secure access is closed to everyone.
Ps: fix_pathinfo disabled

server {
    listen       443 ssl;
    server_name  site.ru;
    root         /var/www/html/;
    ssl_certificate                 /etc/nginx/cert.pem;
    ssl_certificate_key             /etc/nginx/private.key;


    index index.php;

    access_log /var/log/nginx/site-access_log;
    error_log /var/log/nginx/site-error_log;

    add_header Content-Security-Policy "default-src 'self' fonts.gstatic.com chart.googleapis.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' fonts.googleapis.com;";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Frame-Options "deny";
    add_header X-Content-Type-Options "nosniff";

    location = / {
        rewrite ^ $scheme://$host/index.php permanent;
    }

    location / {
        deny all;
        return 404;
    }

    location ~* \.(gif|jpg|png|js|css|ttf|woff|ico)$ {
        try_files $uri =404;
        expires 30d;
    }

    location ~* /secure/ {                                                  
        deny all;                                                              
        return 404;                                                            
    } 

    location ~* ^/index\.php$ {
        try_files $uri $uri/ =404;
        fastcgi_index index.php;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }

    location ~* ^/json\.php$ {
        allow 1.2.3.4;
        deny all;
        try_files $uri $uri/ =404;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }

    location ~* /admin/secure/ {
        deny all;
        return 404;
    }

    location ~* /admin/phpmyadmin/ {
        allow 1.2.3.4;
        deny all;
        try_files $uri $uri/ =404;
        fastcgi_index index.php;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
        add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval';";
    }

    location ~* /admin/ {
        allow 1.2.3.4;
        deny all;
        try_files $uri $uri/ =404;
        fastcgi_index index.php;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }
}

Answer the question

In order to leave comments, you need to log in

3 answer(s)
M
Mikhail Grigoriev, 2017-10-20
@Sleuthhound

1. Permanent redirect from / to index.php
location = / {
        rewrite ^ $scheme://$host/index.php permanent;
    }

    location / {
        deny all;
        return 404;
    }
    location ~* ^/index\.php$ {
        try_files $uri $uri/ =404;
        fastcgi_index index.php;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }

Very strange logic, especially the use of $host and the incorrect use of try_files. Read about try_files here .
$host
in order of precedence: the hostname from the query string, or the hostname from the “Host” field of the request header, or the name of the server matching the request

If you have not defined default_server and server_name site.ru; is the first in the list, then you will receive all requests with any Host field, including an empty one, which is a very dangerous practice.
It is better to use the $server_name variable, but anyway, if you need to process requests for any url only through index.php, then it is done correctly like this:
/NONEXISTENTFILE change to a fake file in advance that cannot exist, for example /d7sdhsdhsdf8sfhgsfd8fh438dfjh
...
        error_page 404 = @cms;

        location / {
            try_files /NONEXISTENTFILE @cms;
        }

        location @cms {
                fastcgi_pass      unix:/var/lib/php5-fpm/xxxxx.sock;
                fastcgi_index    index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root/index.php;
                fastcgi_param   SCRIPT_NAME /index.php;
                include             /etc/nginx/fastcgi_params;
        }
...

2. Disable any static except gif|jpg|png|js|css|ttf|woff|ico
location ~* \.(gif|jpg|png|js|css|ttf|woff|ico)$ {
        try_files $uri =404;
        expires 30d;
    }

It would be more logical and correct to do this:
try_files $uri =404; we won't need it, because we have error_page 404 = @cms; which, if there is no image, will redirect the request to @cms
...
        error_page 404 = @cms;

        location ~* ^.+\.(gif|jpg|png|js|css|ttf|woff|ico)$ {
                expires 30d;
                access_log off;
                log_not_found off;
        }

        location / {
            try_files /NONEXISTENTFILE @cms;
        }
...

Regarding json.php and, in principle, access restrictions, it is more correct to do this through map or geo, for example like this:
http {
....
        geo $my_client_ip $denied {
                default 1;
                127.0.0.1 0;
                XX.XX.XX.XX 0; # <- IP1 с которого можно заходить
                YY.YY.YY.YY 0;    # <- IP2 с которого можно заходить
        }

server {
        listen       443 ssl;
        server_name  site.ru;
        root         /var/www/html/;
...
        set $my_client_ip $remote_addr;
        if ($http_x_forwarded_client_ip ~ "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") {
                set $my_client_ip $http_x_forwarded_client_ip;
        }

        error_page 403 = @deny;

        location @deny {
                root /var/www/deny;
                rewrite ^(.*)$ /index.html break;
        }

        location ~* ^/json\.php$ {
                if ($denied) {
                        return 403;
                }
                try_files /NONEXISTENTFILE @json;
        }

        location @json {
                try_files       $uri = 404;
                fastcgi_pass    unix:/var/lib/php5-fpm/xxxxx.sock;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include         /etc/nginx/fastcgi_params;
        }

}
}

Don't do it! Organize a separate subdomain for phpMyAdmin with a separate virtual server and separate php5-fpm processing through a separate socket.
Never store "left" (auxiliary) utilities in the general directory of a web application, this is a bad practice. For example: If a critical vulnerability is found in phpMyAdmin, then even if access to it from a certain IP is restricted, there is a possibility that it can be exploited and then your web application will be broken through it - do you need it?
About location ~* /admin/ the same as for json.php.
Well, I support all the recommendations above from Kirill Nesmeyanov regarding hsts, ssl, ciphers, dh, ocsp stapling.

K
Kirill Nesmeyanov, 2017-10-20
@SerafimArts

1) There is no hsts header
2) SSL protocols are not specified, it is worth explicitly setting TLS, because the rest are potentially insecure
3) No ciphers enumeration (weak and insecure algorithms are potentially acceptable)
4) No diff key
5) A potentially insecure and outdated version of PHP is used
6) Server headers are enabled (not disabled) (i.e. information about that nginx is used)
7) There are no OCSP Stapling settings (as far as I remember - there may be problems with revoked x.509 certificates)
Regarding the settings of the urls themselves - xs. The best protection and most adequate is to simply store in the web root only what can be accessed outside, as all adequate frames do, for example:https://github.com/laravel/laravel/tree/master/public
This means that in your case there should be only two of them - these are index and json (the second one is simply covered by IP address), the rest of the options do not guarantee anything.
Same thing with phpmyadmin. Why is he? Is it possible that workbench, phpstorm or something else with a connection through an ssh tunnel using a certificate will be less convenient or safer? I do not believe.
I agree with the admin panel, but everything seems to be within the normal range there. It just seems to make sense to reconfigure it in some way (not necessarily right, because I did not check how the example works) in a way:

location ~ /admin/*.\.php$ {
    try_files $uri $uri/ /admin/index.php?$query_string =404;
    // ...
}

Those. so that any request to send somewhere to its index. Not?

P
Pulse, 2017-11-02
@Pulse

why do you need such a monster as phpMyAdmin?
adminer is one file and replaces this whole combine.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question