How To Harden HTTPS in nginx.

I was testing one of my sites using both securityheaders and ssllabs and found that I was being marked down due to weak Diffie-Hellman key exchange, and due to supporting certain weak cryptographic algorithms

I was determined to get an A+ for both sites, and with a bit of trial and error this is how to configure nginx to use strong Diffie-Hillman Parameters, and force the server to only use certain algorithms.

If you are using a proxy as per our other tutorials you will need to treat this new .pem file as you do the web certificate. You need to create it on the Web server then move a copy to the proxy server and point to it as you would with your .cer and.key files.  However the “ssl_ciphers” entry only needs to be on the web server. If this example our site is named ‘site1.com’

To create our new Diffie-Hellman parameters, on the webserver we run

sudo openssl dhparam -out dhsite1params.pem 2048

This will create our .pem file, which we then move to the same location as our .key and .cer files so they can be easily referenced.

The line we need to add is

"ssl_dhparam /etc/nginx/dh/dhsite1params.pem;"

To ensure we are only using strong encryption ciphers we also need to add a few more lines to our site file.

"ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_ciphers'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

ssl_prefer_server_ciphers on;"

 We add them to our site file by opening them with

sudo nano /etc/nginx/sites-available/site1

Then add the previously shown lines to to our SSL configuration as shown below:

#SSL configuration server { access_log off; log_not_found off; 
error_log  logs/yoursite.com-error_log warn;         
listen 443 ssl;         
server_name  site1.com;  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  ssl_prefer_server_ciphers On;  
ssl_ciphers'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH: AES256+EDH';  
ssl_certificate /etc/nginx/cert/crt/yoursite.crt;  
ssl_certificate_key /etc/nginx/cert/key/yoursite.key;  
ssl_dhparam /etc/nginx/dh/dhsite1params.pem;

That’s it.

I ran the scan again and this time A+

Enable HTTPS On nginx Reverse Proxy Server

If a previous post we set up a reverse proxy server see here for the tutorial.

Now we need to configure our sites for HTTPS.

For this to work correctly with no browser warnings when visiting the sites we need the website certificates and private key on both the reverse proxy server and the web server behind. Then we referrence them in the site files.

To configure HTTPS for your nginx server you can follow the tutorial here

Always bypass the proxy during configuration to make sure the site is working correctly over https before moving onto configuring the reverse proxy.

Once you have confirmed your site is working, you need to copy over the certificate and private key over to the reverse proxy server. In this case we put them in a directory’s named /cert/crt and /cert/key.

Open the virtual host file for each site, which if you followed the previous tutorials would be like so.

server { 
listen 80; 
listen 443; 
server_name site1.com; 
ssl_certificate /etc/nginx/cert/crt/site1.cer; 
ssl_certificate_key /etc/nginx/cert/key/site1.key; 
location / { 
proxy_pass https://192.1.1.10; 
proxy_set_header Host $host; 
proxy_set_header X-Real-IP $remoteaddr; 
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
proxy_set_header X-Forwarded-Proto $scheme; 
} 
}

It’s always good to test your configuration after any changes, you do this like so.

sudo nginx -t

Then we need to restart nginx

sudo systemctl reload nginx

We now also need to open port 443 for the reverse proxy server.

We are using ufw so the command is

 sudo ufw allow https

You can now check that only the required ports are open by using

sudo ufw status

You should get something like this:

 80                    ALLOW                 Anywhere
 80 (v6)               ALLOW                 Anywhere
 443                   ALLOW                 Anywhere
 443 (v6)              ALLOW                 Anywhere

That’s it, now you have your sites proxied over https.

Now just repeat for any other sites.

How To Configure nginx As A Reverse Proxy Server

I’m a big fan of Ubuntu and nginx, and in this post we are going to set up a server as a reverse proxy server.

What is, and why would you need, a reverse proxy server?

A reverse proxy server sits between the internet and your web servers to processes requests, perform load balancing and caching if required.

A reverse proxy server can also be used if you only have one external IP address but you want to run multiple websites. Now you can use port forwarding and assign ports for each site but I find this messy, takes more configuration and less than ideal. I don’t want my url to be https://2code-monte.co.uk:8080 for example.

Let’s get to it. In this example we have 1 external IP address but we want to have 2 external websites named “site1.com” which has an internal IP of 192.1.1.10, and “site2.com” which has an internal IP of 192.1.1.11.

On your Ubuntu Server install nginx

sudo apt-get install nginx

Then create a virtual host file for each site.

sudo nano /etc/nginx/sites-available/site1

Enter the following text into the file and save. Then repeat for site2 but replace “server_name” and “proxy_pass” with the appropriate details for site2.

server {
listen 80;
server_name site1.com;

location / {
proxy_pass http://192.1.1.10;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remoteaddr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

In order for the sites to be available we need to create a link to the sites-enabled directory.

sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

sudo ln -s /etc/nginx/sites-available/site2 /etc/nginx/sites-enabled/site2

It’s always good to test your configuration after any changes, you do this like so.

sudo nginx -t

Then we need to restart nginx

sudo systemctl reload nginx

Next we want to lock the server down as it will be proxying all web traffic so we want to make sure our firewall is enabled and only the required ports are open.

In this example we are only running http sites so we only need port 80 open, unless you are connecting via ssh to administer the server.

We are using ufw

sudo ufw enable

sudo ufw allow http

If ssh is needed

sudo ufw allow ssh

You can now check that only the required ports are open by using

sudo ufw status

You should get something like this:

80                    ALLOW                 Anywhere
80 (v6)               ALLOW                 Anywhere

If you have also enabled ssh it will also show up twice.

That’s it. All that is left to do is ensure all web traffic to your external IP goes to the proxy server and it will forward the request. You can add more sites by creating new virtual host files for each new site, but don’t forget to link them to the sites-enabled directory.

In an  up-coming post we will show you how to use https sites over your new nginx proxy server.

New HTTP Header “Referrer Policy”

Another blog by @Scott_Helme here on a new http header we need to be setting so our users info is not leaked to third parties when using a link to another site from our own.

It’s pretty simple, and we are using WordPress on nginx for this example.

First we go to our config file.

sudo nano /etc/nginx/sites-available/default

Then below our existing headers we simply add

add_header Referrer-Policy "strict-origin-when-cross-origin";

then restart nginx and we’re done.

sudo systemctl restart nginx

There are several different options and if you are unsure you should check out the previously mentioned article for a thorough explanation.

How to generate and install SSL certificate in nginx running WordPress

You’ve got your site running on https with a self-signed certificate but it’s time to get one signed by a Certificate authority. If you haven’t already configured your server for https then follow the guide here.

The first thing we need to do is create the private key and csr file. The private key stays on the server, and the csr file is sent to the certificate authority for signing.

First let’s make our directory for the certificates.

sudo mkdir /etc/nginx/certificates/{ssl.key,ssl.crt}

And cd to the new certificates dir

cd /etc/nginx/certificates

Then we run (install openssl if you haven’t already got it)

sudo openssl req -nodes -newkey rsa:2048 -keyout yoursite.key -out yoursite.csr

You will need to answer the questions, and for most of them the answer doesn’t matter. The real important one is “Common Name”. This has to be the name of your website. For example: yoursite.com

If this doesn’t match the name of your site then when anyone goes to your site they will receive a warning that the certificate name doesn’t match the site name.

Now you need to go to where are getting the Certificate from and request a certificate. You will then need to cut and paste all the text from the .csr file into the request. This includes –BEGIN CERTIFICATE– and –END CERTIFICATE–

Once the request is complete you need to download your new certificate as a .crt file and you will probably also have to download the intermediate certificate also as a .crt or .pem.

If there is an intermediate cert you will need to merge them with the following command, or simply copy and paste the text from the intermediate certificate into your site cert.

sudo cat intermediate.pem >> yoursite.crt

Then you need to copy yoursite.key to /etc/nginx/certificates/ssl.crt and yoursite.crt /etc/nginx/certificates/ssl.crt by going to the directory you have the files saved and running

sudo cp yoursite.crt /etc/nginx/certificates/ssl.crt

sudo cp yoursite.key /etc/nginx/certificates/ssl.key

Then in your config file you reference the se locations so the server knows which key and crt file to use.

For example

sudo nano /etc/nginx/sites-available/default

Then add the locations in the SSL Server tags as below

ssl on;

ssl_certificate /etc/nginx/certificates/ssl.crt/yoursite.crt; ssl_certificate_key /etc/nginx/certificates/ssl.key/yoursite.key;

Save the changes then retsrat the webserver service

sudo systemctl restart nginx

Now browse to your site over https.

Moving WordPress to https

It’s time to move your sites over to https. Again for this example we will be using a LEMP stack on Ubuntu 16.04.2.

First thing is to open the firewall to allow traffic on port 443. (Here I’m using ufw)

sudo ufw allow https

Then we can check to ensure the port is open with the following.

sudo ufw status

Once we’ve done that we add the following to our config file.

For nginx this is /etc/nginx/sites-available

To open the file

sudo nano yoursite

SSL configuration
server {
access_log off;
log_not_found off;
error_log  logs/yoursite.com-error_log warn;

        listen 443 ssl;
        server_name  yoursite.com; 

 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers On;
 ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:
DH+AES256:
ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:
RSA+AES:
RSA+3DES:!aNULL:!MD5:!DSS;
ssl_certificate /etc/nginx/cert/crt/yoursite.crt;
ssl_certificate_key /etc/nginx/cert/key/yoursite.key;

The “ssl_certificate” and “ssl_certificate_key” entries are the key and certificate that you have generated on the server and had signed by a certificate authority. If you don’t have these then you can check out our guide on how to do this here.

Now you will be able to access the site over both http and https.

How to update WordPress over SSH

You should definitely be updating over an encrypted connection, so with that in mind, let’s proceed.

Run the following command, and answer all the questions.

sudo adduser wp-admin

Next we need to change ownership of the directory, so we run

cd /var/www/html

sudo chown -R wp-admin:wp-admin /var/www/html

Now we create the SSH keys, while logged in as the new user

sudo su - wp-admin

ssh-keygen -t rsa -b 4096

Save them on the server, for example

sudo /home/wp-admin/wp_rsa

Then go back to your normal account with the following

exit

Then we lock down the permissions

sudo chown wp-user:www-data /home/wp-user/wp_rsa*

sudo chmod 0640 /home/wp-user/wp_rsa*

Now we create a new directory, and set permissions for storing the keys to allow remote login

sudo mkdir /home/wp-user/.ssh

sudo chown wp-user:wp-user /home/wp-user/.ssh/

sudo chmod 0700 /home/wp-user/.ssh/

Then we copy the keys over to the new directory

sudo cp /home/wp-user/wp_rsa.pub /home/wp-user/.ssh/authorized_keys

Then we lock down the new files

sudo chown wp-user:wp-user /home/wp-user/.ssh/authorized_keys
sudo chmod 0644 /home/wp-user/.ssh/authorized_keys

Then we lock down to local login only

sudo nano /home/wp-user/.ssh/authorized_keys

Put the following at the top of the file

 from="127.0.0.1"

Now we update the wp-config file, open with the following

sudo nano /var/www/html/wp-config.php

Then add these lines to the file

define('FTP_PUBKEY','/home/wp-user/wp_rsa.pub');

define('FTP_PRIKEY','/home/wp-user/wp_rsa');

define('FTP_USER','wp-user');

define('FTP_PASS','');

define('FTP_HOST','127.0.0.1:22');

Then restart nginx

sudo systemctl restart nginx

Then next time you need to update select ssh enter your new wp-admin user details and you should be good to go.

Before we finish more thing.

As we have locked down the permissions quite tightly you may need to change ownership of the html folder temporarily while running updates, and then reinstate the locked down permissions and you do this by running the following.

Before updating run.

sudo chown -R www-data /var/www/html

Then once your done

sudo chown -R wp-admin /var/www/html

Protect WordPress Login page and admin panel

If you run a WordPress site and have any knid of network monitoring you’ll see endless brute force attempts on your login page.

A strong password will only do so much, so really you want the login page to only be available to legitimate IP adresses.

This is easy to achieve and offers great protection as if someone requests the login page but they are not connecting from a listed IP the page will not show.

First we need to go to our active config file. On LEMP running wordpress we go to.

sudo nano /etc/nginx/sites-enabled/wordpress

Then we add the following to our server block in its own location container. (Replace 11.11.11.111 with your own IP)

location ~ ^/(wp-admin|wp-login.php){

allow 11.11.11.111;

deny all;}

And that’s it. You can access your login page and admin panel, but it’s not vailable to the rest of the internet. If your site is not using https then you should seriously consider setting up a VPN to login and administer your site.

 

 

Custom HTTP Headers (nginx, WordPress)

If you have a website and you are not implementing some custom headers you may want to look into it. These are not set by default and can help in protecting your site from all types of attacks.

To start with go to https://securityheaders.io/ and scan your site. If you have none of these headers implemented your site will score very poorly. Don’t forget with these headers we aren’y only protecting our site, but also our site visitors as well.

The ones we are going to look at are for protecting against Cross-Site-Scripting, malicious content, drive-by downloads, and stop your site being viewed in an iframe.  We will also stop the site advertising Server and software details, for example the Server build, and php version. The reason for this is not covered here but if you are intersted you can go here and read this brilliant  article by @Scott_Helme.

For today we will be concetrating on WordPress installed on LEMP stack. Firstly we need to backup our config, and then go to our active config file. (yours may be different depending on your setup). Also note that the Strict Transport Security header is for https sites only.

sudo nano /etc/nginx/sites-enabled/wordpress

Then in our http server block we add the following. ( if using https on your site then put the headers in both blocks.

add_header X-Frame-Options "SAMEORIGIN";

add_header Content-Security-Policy "default-src yoursite.com";

add_header X-Xss-Protection "1; mode=block";

add_header X-Content-Type-Options "nosniff";

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

server_tokens off;

You can see the layout in the screenshot.

We save the changes then run the following to test for typo’s.

sudo nginx -t

Then

sudo service nginx restart

Finally we go change a setting in the php.ini file for our version to disable version broadcast.

sudo nano /etc/php5/fpm/php.ini

We need to change

expose_php = on

To

expose_php = off

Save and close the file and restart php

sudo service php5-fpm restart

To check all this is working correctly go back to Security Headers and check your site again. Your sites rating should be significantly better.

More WordPress Issues

It seems barely a week goes by without having to resolve a WordPress Issue. I needed to update to version 4.8, so I went to my update panel ready to use the “one click” update, but instead of opening up the update page informing me that the site is in update mode it opened to a blank page. After refreshing and returning to the update panel, I disabled all pluggins and tried again, now whenever clicking the update button I was greeted with a message telling me an update was in progress, I therefore it left it expecting that it would just eventually complete. However 12 hours later and WordPress was still not updated and clicking the update buttomn gave the same message that an update was already in process! Restarted the server no change, a bit of googling led me to https://wordpress.stackexchange.com/questions/224989/get-rid-of-another-update-is-currently-in-progress  I therefore installed wp-cli using this guide https://www.sitepoint.com/wp-cli/ and tried in vain to carry out these steps. However I was continually told by wp-cli that wp-config.php did not exist! I checked and this was not the case, so another brick wall! I had already wasted an hour by this point on what should have been a ten minute job. Therefore I simply downloaded the latest WordPress version by running

wget http://wordpress.org/latest.tar.gz

then (from the same dir)

tar xzvf latest.tar.gz

then

sudo rsync -avP ~/wordpress/ /var/www/html/

As I had manually created an uploads directory I had to reassign group ownership to allow me to upload content to that directory using the following.

sudo chown -R :www-data /var/www/html/wp-content/uploads

Hey presto! we are now running on the latest version, with all existing pluggins and content still working. (I double-checked by running wpscan from my kali box just to be sure I was on the latest version) Hope this helps someone else out. Don’t forget to backup before running these steps.