Published: 2015-Oct-23
Updated: 2016-May-11
Applications configured
Ubuntu Server 14.04 – 16.04
Apache 2.4.20
MySQL 5.6.28
PHP 7.0.5
OpenSSL 1.0.2g
WordPress 4.5
Sendmail 8.14.9
OSSEC 2.8.3
apt-transport-tor 0.2.1
Let’s Encrypt
The TLS configuration in this article will get you a Qualys SSL Labs perfect A+ (100/100/100/100).
Things that should be in this guide but I still haven’t learned well:
— HPKP (key pinning, it still scares me)
— DNSSEC (my registrar doesn’t support it, serious wtf)
— Grsecurity kernel patches
— Torify Sendmail
Regularly test your transport security here: https://www.ssllabs.com/ssltest/analyze.html
Regularly test your security headers here: https://securityheaders.io/
SSH
sudo vim /etc/ssh/sshd_config
Comment out these lines:
#HostKey /etc/ssh/ssh_host_dsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
Edit these lines:
ServerKeyBits 4096
LoginGraceTime 30
PermitRootLogin no
Add these lines:
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Done. Then:
sudo service ssh restart
Firewall
sudo ufw limit 22/tcp
sudo ufw allow 443/tcp
sudo ufw allow out 22/tcp
sudo ufw allow out 25/tcp
sudo ufw allow out 53/udp
sudo ufw allow out 443/tcp
sudo ufw allow out 9050/tcp
sudo ufw deny out to any
sudo ufw enable
sudo ufw status verbose
Or in one line…
sudo ufw limit 22/tcp && sudo ufw allow 443/tcp && sudo ufw allow out 22/tcp && sudo ufw allow out 25/tcp && sudo ufw allow out 53/udp && sudo ufw allow out 443/tcp && sudo ufw allow out 9050/tcp && sudo ufw deny out to any && sudo ufw enable && sudo ufw status verbose
This UFW (iptables) rule set makes it so brute forcing SSH won’t work and allows all HTTPS traffic. These rules also make it so no traffic can leave the web server unless it is SSH (22), SMTP (25), DNS (53), HTTPS (443), or Tor Socks (9050) traffic. Most web servers do not go as far as block all outbound traffic by default, but it is important in case the web server does become compromised.
I would usually allow outbound HTTP traffic because the Ubuntu update repositories require HTTP… they’re not HTTPS which is extremely sad. However, we will be Torifying Apt so that’s why we allow outbound 9050/tcp. If you don’t want to Torify Apt, you’ll need to allow outbound 80/tcp instead of 9050/tcp.
I allow outbound SMTP traffic because I use Sendmail to send TLS encrypted email notifications.
I do not allow any inbound HTTP traffic, and it is because of HSTS Preload. All major browsers know to connect to my website via HTTPS and not HTTP.
Patch (securely)
Make sure your OS is current then restart:
sudo apt-get update
sudo apt-get dist-upgrade
sudo shutdown -r now
Get updates over Tor instead of HTTP. First install and configure Tor.
sudo vim /etc/apt/sources.list
Add these lines to the bottom:
deb http://deb.torproject.org/torproject.org wily main
Then:
sudo gpg --keyserver keys.gnupg.net --recv 886DDD89
sudo gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | sudo apt-key add -
sudo apt-get install tor deb.torproject.org-keyring apt-transport-tor -V
Backup sources.list:
sudo cp /etc/apt/sources.list /etc/apt/sources.bak
Edit sources.list with tor+http:
sudo vim /etc/apt/sources.list
For example (add “tor+” to all active lines):
deb tor+http://us.archive.ubuntu.com/ubuntu/ wily main restricted
Add these PPAs so that Apache, MySQL and PHP will be up to date:
sudo add-apt-repository ppa:ondrej/apache2
sudo add-apt-repository ppa:ondrej/mysql-5.6
sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
Be sure to Torify the new PPAs.
sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-apache2-wily.list
deb tor+http://ppa.launchpad.net/ondrej/apache2/ubuntu wily main
And:
sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-mysql-5_6-wily.list
deb tor+http://ppa.launchpad.net/ondrej/mysql-5.6/ubuntu wily main
And:
sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-php-wily.list
deb tor+http://ppa.launchpad.net/ondrej/php/ubuntu wily main
Done. Then:
sudo apt-get update
All repositories should now read tor+http://… because they are routed through Tor. If any hang, it is likely because a repo was missed (and needs tor+) and is trying to connect using outbound HTTP port 80.
Now install the apps:
sudo apt-get update && sudo apt-get install apache2 libapache2-mod-evasive unzip nghttp2 nghttp2-server mysql-server-5.6 php7.0 sendmail -V
Sendmail config
sudo vim /etc/mail/sendmail.cf
Edit or add these lines:
O CipherList=ECDH+AES256:!3DES:!aNULL:!DES:!DSS:!eNULL:!EXP:!IDEA:!LOW:!MD5:!PSK:!RC4:!SEED
O ServerSSLOptions=+SSL_OP_NO_SSLv2 +SSL_OP_NO_SSLv3 +SSL_OP_NO_TLSv1 +SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS +SSL_OP_CIPHER_SERVER_PREFERENCE
O ClientSSLOptions=+SSL_OP_NO_SSLv2 +SSL_OP_NO_SSLv3 +SSL_OP_NO_TLSv1 +SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS +SSL_OP_CIPHER_SERVER_PREFERENCE
This configuration assures that TLS will be used when sending mail to a destination mail server (not STARTTLS!). However, you’d still be using self-signed certs. Regardless, ECDH+AES256 is the only cipher being set because I know my email server receiving my alerts will use it.
sudo service sendmail restart
I can verify the TLS encryption in my received email headers:
Received: from yawnbox.com (mail.yawnbox.com. [38.140.26.46])
(version=TLS1_2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256/256);
After you install Let’s Encrypt certs, you may want to add these lines:
O ServerCertFile=/etc/letsencrypt/live/example.com/fullchain.pem
O ServerKeyFile=/etc/letsencrypt/live/example.com/privkey.pem
O DHParameters=/etc/ssl/private/dhparams_4096.dh.param
This would configure Sendmail to use the same certs that you will use for HTTPS, which is fine since it should be from the same TLD. However, if you have a mail server with valid certs already, you should use those certs instead here (like if your mail server certs are for mail.example.com).
Then:
vim /etc/php/7.0/apache2/php.ini
Uncomment and edit this line:
sendmail_path = /usr/sbin/sendmail -t -i
Then:
sudo service apache2 restart
Apache config
sudo vim /etc/apache2/mods-available/evasive.conf
Uncomment the first 6 lines. Change if needed. Edit the DOSEmailNotify line if you’d like to be alerted when a specific IP address gets blocked. Then:
sudo vim /etc/apache2/apache2.conf
Add these lines:
FileETag None
Header unset ETag
AuthnCacheSOCache shmcb
Edit this line:
Timeout 30
Done. Then:
sudo a2enmod headers http2 ssl socache_shmcb
Done. Then:
sudo vim /etc/apache2/conf-available/security.conf
Uncomment these lines:
Directory /
AllowOverride None
Require all denied
/Directory
Add these lines:
Header always set X-XSS-Protection: "1; mode=block"
Header always set X-Permitted-Cross-Domain-Policies: "master-only"
Header always set Cache-Control: "private, no-cache, no-store, must-revalidate"
Header always set Pragma: "no-cache"
Header always set Expires: "-1"
Header always set X-Content-Type-Options: "nosniff"
Header always set X-Frame-Options: "sameorigin"
Header always set Content-Security-Policy: "default-src 'self'"
Edit these lines:
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Done. Then:
sudo service apache2 restart
Apache TLS config
Start by Generating 4096-bit DHparams. This will take a while (5+ minutes) depending on your server’s processing capabilities. These three lines may be something worth scripting to recreate every month via cron:
sudo openssl dhparam -out /etc/ssl/private/dhparams_4096.tmp 4096
sudo mv /etc/ssl/private/dhparams_4096.tmp /etc/ssl/private/dhparams_4096.pem
sudo cp /etc/ssl/private/dhparams_4096.pem /etc/ssl/private/dhparams_4096.dh.param
Done. Then:
sudo vim /etc/apache2/mods-enabled/ssl.conf
Add these lines:
SSLCompression off
SSLSessionTickets off
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000)
SSLOpenSSLConfCmd DHParameters /etc/ssl/private/dhparams_4096.pem
SSLOpenSSLConfCmd Curves secp384r1
Change these lines:
SSLRandomSeed startup file:/dev/urandom 4096
SSLRandomSeed connect file:/dev/urandom 4096
Uncomment and/or edit these lines:
SSLCipherSuite HIGH:!3DES:!aNULL:!DES:!DSS:!eNULL:!EXP:!IDEA:!LOW:!MD5:!PSK:!RC4:!SEED
SSLHonorCipherOrder on
SSLProtocol -all +TLSv1.2
Let’s Encrypt will modify your Apache default-ssl.conf file for you. After installing Let’s Encrypt, I made sure to create 4096-bit keys:
sudo ./letsencrypt-auto --apache -d example.com -d www.example.com --rsa-key-size 4096
Keep an eye on ECDSA key support.
Done. Then:
sudo vim /etc/apache2/sites-available/default-ssl.conf
Add these lines:
SSLEngine on
Header edit Set-Cookie ^(.*)$ $1;Secure;SameSite=Strict
Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
Add these lines under “/Directory” but before “/VirtualHost”:
#Protocols h2 http/1.1
H2Direct on
H2MaxSessionStreams 20
H2MaxWorkerIdleSeconds 20
H2MaxWorkers 20
H2MinWorkers 10
H2ModernTLSOnly on
H2SerializeHeaders off
H2SessionExtraFiles 10
H2StreamMaxMemSize 128000
H2TLSCoolDownSecs 0
H2TLSWarmUpSize 0
H2Upgrade on
H2WindowSize 128000
Now that HSTS and HSTS Preload are configured, submit your domain to Google for getting added to the Chrome HSTS Preload list:
https://hstspreload.appspot.com/
Doing so will eventually replicate to all other mainstream browsers. My experieince was that after submitting my domain for Preload, I had to wait a couple of weeks for it to show up in Chrome, and a couple more weeks to show up in Firefox. Tor Browser took the longest. Just be patient.
I keep the “Protocols h2 http/1.1” directive commented out because I can’t achieve a perfect Qualys SSL Labs score and force HTTP2. When I do, Chrome sometimes doesn’t load the website because the HTTP2 specification doesn’t accept some of the cipher suites with this configuration.
Also check out HTTP2 Explained.
Done. Then:
sudo a2ensite default-ssl.conf
sudo a2dissite 000-default.conf
sudo service apache2 restart
Basic MySQL hardening
sudo apt-get install mysqltuner
sudo mysqloptimize -p -u root -A -o
sudo mysqltuner
Make any of the recommended changes (then re-run mysqltuner):
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
WordPress
sudo vim /var/www/example.com/wp-config.php
Add these lines:
define('WP_MEMORY_LIMIT', '2G');
define('WP_MAX_MEMORY_LIMIT', '2G');
The WP_MAX_MEMORY_LIMIT specifically addresses the administrative backend, which has a different memory limit from both WordPress and PHP configs.
Then, disable the HTML5 canvas data issue (noticeable in Tor Browser):
(thanks Wilton)
wp-admin > Appearance > Editor > functions.php
Add this to the bottom:
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
OSSEC and Sucuri
OSSEC is a powerful Host-based Intrusion Detection System (HIDS). Sucuri is a powerful WordPress plugin. This section gets both installed and gets them working together.
wget -U ossec https://bintray.com/artifact/download/ossec/ossec-hids/ossec-hids-2.8.3.tar.gz
tar -zxvf ossec-hids-*.tar.gz
cd ossec-hids-2.8.3/
sudo ./install.sh
During installation:
en
1- local
2- accept default
3- accept all defaults
Then:
sudo /var/ossec/bin/ossec-control start
sudo vim /var/ossec/etc/ossec.conf
Verify/change/add these lines:
email_notification yes /email_notification
email_to email@email.com /email_to
smtp_server localhost /smtp_server
email_from ossecm@email.com /email_from
This configuration should force the use of TLS encrypted emails if Sendmail is installed and configured.
sudo service ossec restart
Then, in the WordPress Dashboard, install and activate the “Sucuri Security” plugin.
Open the Sucuri Security dashboard and generate an API key. It will use your WordPress admin account/email. I had to manually email the provided email to get a key.
Then:
sudo touch /var/log/wordpress.log
sudo chown www-data:www-data /var/log/wordpress.log
In WordPress Dashboard, navigate to Sucuri Security > Settings > Log Exporter
Enter “/var/log/wordpress.log” and save.
Then:
sudo vim /var/ossec/etc/ossec.conf
Under “Files to monitor (localfiles)“, add these lines:
localfile
log_format syslog /log_format
location /var/log/wordpress.log /location
/localfile
sudo service ossec restart
Be prepared for lots of email notifications, thankfully TLS encrypted. Be careful about what information is copied and stored by your email provider.
WordPress Plugins
— Disable Comments
— Disable Google Fonts
— Disable XML-RPC