Nginx config to prevent invalid sock file access attacks

Nginx config to prevent invalid sock file access attacks

Save common attacks by rightly configuring Nginx config

Once your site goes live there are some elements out there who keep trying to access your filesystem using an old UNIX hack trying to hit your site with a path to a sock file. Using frameworks like Django can prevent this already if you have your DISALLOWED_HOST settings set properly to only allow valid hostnames in it. But the first line of defense for this must be the webserver i.e. Nginx. The assumption is here that you are using Nginx as your reverse proxy server.

Depending on your configuration your conf files for Nginx will be in either /etc/nginx/conf.d/ or as a link in /etc/nginx/sites-enabled/. Modify your instructions accordingly i.e. updating files directly in /etc/nginx/conf.d/ or if its configured to be so, update /etc/nginx/sites-available/ and link to /etc/nginx/sites-enabled/ directory. Me personally recommending to have configured the latter flow for production setups.

Your typical Nginx config for a production reverse-proxy (with letsencrypt) will look something like this:

server {
    server_name mysite.com;
    client_max_body_size 100M;
    access_log /var/log/mysite/nginx.access.log;
    error_log /var/log/mysite/nginx.error.log;
    location / {
            include proxy_params;
            proxy_pass http://unix:/tmp/mysite.sock;
    }
    ...
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot}server {
    if ($host = mysite.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    server_name mysite.com;
    return 404; # managed by Certbot
}

After configuring this you might be taking a breath of relief that your configuration is done only to find the following error keeps popping up in your application server log history:

[15/Jan/2021:21:49:49 +0530] “GET /account/login/ HTTP/1.0” 200 2348 “-” “Mozilla/5.0 (iPad; CPU OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1” in 109739µs
ERROR 2021–01–16 06:15:07,816 [none] 127579 — — 88 [django.security.DisallowedHost] response_for_exception : Invalid HTTP_HOST header: ‘/tmp/mysite.sock:’. The domain name provided is not valid according to RFC 1034/1035.
WARNING 2021–01–16 06:15:07,945 [none] 127579 — — 224 [django.request] log_response : Bad Request: /
[16/Jan/2021:06:15:07 +0530] “GET / HTTP/1.0” 400 143 “-” “-” in 129680µs

In short, someone tried accessing your site using your site's URL but with a server-name of /tmp/mysite.sock?

If you recheck your Nginx config you will be scratching your head thinking you have specifically told it in both the above server blocks to only entertain server_name mysite.com then how in the world is this request reaching your application server?

The magic happening here is that there is no default server defined in your Nginx config and hence if it fails to find any server block in all config files then it by default chooses to use your only configured block even when you have written it for mysite.com only. In short, the **server_name** directive in the block acts only as a tag and not as a gatekeeper.

The way to go about fixing this is to define default server blocks so that Nginx will choose them over your mysite.com blocks. Within those server blocks you can then reject the requests and close the connection from Nginx itself before it even reaches your application server.

Usually, I use the file default.conf for this which Nginx creates for you as a sample on a fresh install. It doesn’t matter in which file you write, this is just a convention I like to follow. Backup the default conf file just in case and then replace its contents with the following:

server {
    listen      80 default_server;
    listen      [::]:80 default_server;
    server_name _;
    return      444;
}
server {
        listen          443 ssl default_server;
        listen          [::]:443 default_server;
        server_name     _;
        ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
        return          444;
}

Note that the server_name directive is just a placeholder here and it’s value can be anything, but just for a convention I am using _ as its value will definitely not clash with any of my other server blocks.

Just as a precautionary measure I also edit my mysite.com server block just for peace of my mind:

server {
    server_name mysite.com;
    if ($host != "mysite.com") {
        return 444;
    }
    ... # same other configs as before
}

Save all these configs and reload nginx server as follows:

sudo nginx -t  
sudo nginx -s reload

And now finally Nginx configs will behave according to your expectation with no further magic.