Categories
Security

How an NGINX Config Error can cause a Security Breach

If you’re not familiar with NGINX, it is a very popular bit of software powering millions of web servers. In a nut shell, it is designed to be very efficient and is used on a lot of high traffic websites, such as Twitch, WordPress, and DropBox.

It’s not particularly difficult to install and configure, but I suppose it requires a bit more attention compared to Apache.

As I have recently learned, a slight misconfiguration can cause a security breach if you are not careful. This is to do with HTTP Basic Authorisation configuration within NGINX, and how NGINX prioritises ‘location’ segments within configuration files.

One of the reasons NGINX is so fast is that unlike Apache, it does not support the use of .htaccess files. As NGINX is not having to scan each folder on the web server for the presence of htaccess files, it gains a speed advantage. Normally, on an Apache web server, if you want to enable HTTP Basic Authentication so that users are required to enter a username/password for certain directories, you can use .htaccess files. This is not an option with NGINX, so it has to go into the main configuration file on the web server.

Example Config File

 location / {
 try_files $uri $uri/ /index.php?q=$uri&$args;
 }

 location /secretarea {

        auth_basic "Administrator Login";
	auth_basic_user_file /home/secureuser/.htpasswd;
 
 }

 location ~* \.php$ {
	# With php-fpm unix sockets
	fastcgi_pass phpfpm;
	include         fastcgi_params;
	fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
	fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
 }

Looking at the above config file, you can see that the main directory (/) is open access and does not require authentication. You may also assume that /secretarea is password protected (and you would be right, to an extent).

If you were to use this configuration, and then visit https://www.example-domain-name.com/secretarea/, you would be prompted for a password, as expected.

However, if you were to visit https://www.example-domain-name.com/secretarea/phpfile.php, you will be granted access straight away without being prompted for a password. This can cause a bit of a problem as it may not be immediately obvious that you have a problem.

The reason this occurs is because NGINX chooses one location block (based on a series of rules), and only one location block will win. When you’re visiting the PHP file directly, the location block at the bottom of the config file is taking precedence and therefore bypassing the location block requiring authorisation.

How to mitigate this?

To mitigate this, we first need to understand how NGINX prioritises location blocks.

Here is an extract written by Martin Redmond from a helpful Stack Overflow article:

location  = / {
  # matches the query / only.
  [ configuration A ] 
}
location  / {
  # matches any query, since all queries begin with /, but regular
  # expressions and any longer conventional blocks will be
  # matched first.
  [ configuration B ] 
}
location /documents/ {
  # matches any query beginning with /documents/ and continues searching,
  # so regular expressions will be checked. This will be matched only if
  # regular expressions don't find a match.
  [ configuration C ] 
}
location ^~ /images/ {
  # matches any query beginning with /images/ and halts searching,
  # so regular expressions will not be checked.
  [ configuration D ] 
}
location ~* \.(gif|jpg|jpeg)$ {
  # matches any request ending in gif, jpg, or jpeg. However, all
  # requests to the /images/ directory will be handled by
  # Configuration D.   
  [ configuration E ] 
}

The reason the mistake occurred in my example is because regular expression location blocks are matched first, whereas ‘configuration c’ style location blocks (as in our example) would have been de-prioritised.

To mitigate this, we can nest a location block within a location block. Or, in other words, include the PHP location block in our /secretarea block too, as well as it being a separate/independent block at the bottom of the config file for the main part of the website.

 location / {
 try_files $uri $uri/ /index.php?q=$uri&$args;
 }

 location /secretarea {

 location ~* \.php$ {
	# With php-fpm unix sockets
	fastcgi_pass phpfpm;
	include         fastcgi_params;
	fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
	fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
 }

        auth_basic "Administrator Login";
	auth_basic_user_file /home/secureuser/.htpasswd;
 
 }

 location ~* \.php$ {
	# With php-fpm unix sockets
	fastcgi_pass phpfpm;
	include         fastcgi_params;
	fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
	fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
 }

As NGINX will first identify the PHP regular expression block nested in our ‘secretarea’ location block first, it will take precedence over the non-nested block at the bottom of the configuration file, and therefore the authentication settings will be inherited from the parent block and apply to our PHP files too. Quite an easy mistake to make!