Security for everyone

Common Nginx Misconfigurations and Hardening Tips

SecurityForEveryone

Security for Everyone

02/May/21

As of March 2021, one in three websites on the internet runs on Nginx, according to a web survey by Netcraft. Nginx web server powers high-performance applications in a responsive, efficient manner and is useful for load balancing, HTTP caching, mail proxying, and reverse proxying. With the ability to handle 40,000 inactive HTTP connections with just 10Mb of memory, it is the go-to choice for high-traffic sites. This post covers the Common Nginx misconfigurations that leave your web server open to attack and include practical hardening tips to improve your cybersecurity posture.

Hardening Tips for Nginx Security Misconfiguration

 

Disable Nginx server_tokens

Information disclosure is a blessing to attackers; finding out the version of Nginx you are running enables them to choose just the right vulnerability to exploit! As such it is recommended that you configure your Nginx webserver to not display its version in the server header by editing the nginx.conf file as follows: 

 server_tokens off;

 

Hide proxy headers

Disable the signature of the platform you are running on e.g Apache or PHP to prevent information disclosure using:

 proxy_hide_header X-Powered-By;

 

Configure Nginx to use security headers

Add an X-frame-options header to prevent clickjacking attacks.

 add_header X-Frame-Options SAMEORIGIN;

 

Enforce the use of TLS

You can instruct browsers to only use TLS connections for your site using the strict-transport-security header as follows:

 add_header Strict-Transport-Security max-age=15768000

If forcing connections to be over TLS is not desirable for your use case, you can instead listen on 443 (HTTPS) instead of 80(HTTP) on a per-server basis as follows:

 server { 

      # server listening for SSL traffic on port 443 

		<p>&nbsp; &nbsp; &nbsp; listen 443 ssl;&nbsp;</p>

		<p>}</p>
		</td>
	</tr>
</tbody>

 

Limit the use of unwanted HTTP methods

Disable the HTTP methods you have no intention of using on your web server by adding this condition in the location block of the config file.

 location / { 

		<p>&nbsp; &nbsp; &nbsp; #only allow GET HEAD POST methods&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; limit_except GET HEAD POST {deny all;}&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

Restrict Access by IP or password

Nginx gives you the option of whitelisting IP access to select sections of your website, which you can utilize as follows:

 location /admin-only { 

		<p>&nbsp; &nbsp; &nbsp;#only allow access from the IPs defined&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;allow 10.5.6.7; # allows this one IP access allow</p>

		<p>&nbsp; &nbsp; &nbsp;192.168.100.0/24; # allows this IP range access&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;deny all; # denies every other IP access</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

Additionally, Nginx can also enforce access based on passwords like Apache does with .htaccess and .htpasswd files.

 location /admin-only { 

		<p>&nbsp; &nbsp; &nbsp;#only allow access from the users defined in the password</p>

		<p>&nbsp; &nbsp; &nbsp;file auth_basic&nbsp; &uml;&nbsp;Administrators lounge&uml;;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;auth_basic_user_file /path/to/password/file;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

The password file would look as follows:

 user:password 

		<p>&nbsp;naruto:h1n4t4&nbsp;</p>

		<p>&nbsp;sanji:Nam1is4maz4ng&nbsp;</p>

		<p>&nbsp;zoro:1w1llb3th3strong3stsw0rdsm4n1nth3w0rld</p>
		</td>
	</tr>
</tbody>

 

Common Nginx Misconfigurations

 

Passing Uncontrolled Requests to PHP 

Most Nginx example configs for PHP advocate for passing every URI ending in .php to the PHP interpreter which could result in arbitrary code execution by third parties on most PHP setups.

 location ~* \.php$ { 

		<p>&nbsp; &nbsp;&nbsp; fastcgi_pass backend;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;# additional code here&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

In this example, all requests that the .php file extension will be passed to the FastCGI backend. A default PHP configuration is set so that it attempts to guess the file you want to execute in cases where the full path specified does not lead to a file that exists on the system. Let's say you request for /community/anime/naruto.php, which does not exist while /community/anime/naruto.gif actually does exist; the PHP interpreter will process /community/anime/naruto.gif. If naruto.gif contains embedded PHP code, it will execute. 

Countermeasures:

● Set cgi.fix_pathinfo=0 in php.ini which causes the PHP interpreter to try only the literal path given and stop processing if the file does not exist. 

● disable the execution of PHP files in directories with user uploads.

 location /uploads { 

		<p>&nbsp; &nbsp;&nbsp; Location ~ \.php$ {return 403;}&nbsp;</p>

		<p>&nbsp; &nbsp;&nbsp; # 403 is Access Forbidden&nbsp;</p>

		<p>&nbsp; }</p>
		</td>
	</tr>
</tbody>

● Set Nginx to only pass certain PHP files for execution.

 location ~* (file1|file2|file3) \.php$ { 

		<p>&nbsp;fastcgi_pass backend;&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

Missing Root Location

The root directive is positioning in your configuration matters. One of the Nginx configuration pitfalls that administrators are strongly warned against is putting the root directive inside location blocks. If you add root to every location block individually, then an unmatched location block will lack root, which would cause errors. Conversely, failure to put the root directive in a location block would give access to the root folder of the server block.

 server { 

		<p>&nbsp; &nbsp; &nbsp; &nbsp;root /etc/nginx/app;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp;location /welcome.jpeg{&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;try_files $uri $uri/ =403;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;proxy_pass http://127.0.0.1:8088/;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp;}&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

In the above example, the root folder is /etc/nginx/app meaning that files in this folder are available to us. However there is no location for / i.e location / { } but only for /welcome.jpeg. As such, a request like GET ../nginx.conf would show the content of the config file etc/nginx/nginx.conf 

As such, requests to / will take you to the path specified in the root directive which is globally set. 

Countermeasures: 

● make sure to set the root directive for the server block. 

● Set the respective root directives for the location blocks as needed.

Using non-standard document root locations 

Deviating from the standard root document locations laid out in the Filesystem Hierarchy Standard might seem like a fun idea sometimes. That is of course until someone requests for a file they should not be able to access and you end up getting compromised. 

 server { 

		<p>&nbsp; &nbsp; &nbsp;root /;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;location / {&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try_files /var/www/html/$uri $uri @php;&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp;}&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; location @php {&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # additional code here&nbsp;</p>

		<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;</p>

		<p>}</p>
		</td>
	</tr>
</tbody>

 

In the above example, a request for /pizza would be passed to PHP because the file is not found. However, a request for /etc/passwd would reveal your etc/passwd file meaning attackers would have your user list and password hashes and if your Nginx workers run as root, how your passwords have been hashed as well. 

Countermeasures: 

● Stick to the standard places to place your web content like /usr/share/www, /srv/, /var/www 

● Set your Nginx workers to run as non-privileged users instead of as root or a user with Sudo privileges as shown below 

 

Use the user directive in the /etc/nginx/nginx.conf config file. 

 #user nobody;

 

Uncomment it and change nobody to the user you created

 user fornginx;

 

Misconfigured Nginx alias & Merge slashes set to off 

 

The alias directive in Nginx directs which replacement should be used in place of the specified location. A misconfigured alias allows attackers to access files outside the target folder by allowing directory traversal. 

  location /naruto/ { 

		<p>&nbsp; &nbsp; &nbsp; &nbsp;alias /cup/ramen/;&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>

 

In this example, when a request to /naruto/smile.jpeg is made, the file /cup/ramen/smile.jpeg will be sent instead. However, when the location does not end with the directory separator /, 

 location /naruto { 

		<p>&nbsp; &nbsp; &nbsp; alias /cup/ramen/;&nbsp;</p>

		<p>&nbsp;}</p>
		</td>
	</tr>
</tbody>


a request to /naruto../app/config.py will send the file /cup/app/config.py 
 

The Merge slashes directive, usually on by default, is the mechanism used to compress multiple forward slashes into one such that // or //// becomes /. When the misconfigured Nginx alias is coupled with merge slashes set to off, an attacker is able to traverse the system quickly, and in the case where Nginx is used for reverse proxying an application vulnerable to local file inclusion, the use of multiple slashes in the request is an easy way to exploit it. 

Countermeasures: 

● Set merge_slashes to on 

● Ensure all parent prefixed locations for Nginx alias directives end with a directory separator.

 

Conclusion 

With the five hardening tips mentioned above, your cybersecurity posture should improve considerably. Also, remember to keep your Nginx web server up to date and visit this blog for more exciting cybersecurity stories and helpful tips. Till next time, stop using default passwords!

 

cyber security services for everyone one. Free security tools, continuous vulnerability scanning and many more.
Try it yourself,
control security posture