This article is in process of being written by Tin.
Introduction
As an application, WordPress is easy to use and feature rich. It has an established community with the best selection of themes for any blogging or cms software. However, from an administrator perspective, though the initial setup seems fast and easy, it is very insecure.
This approach to setting up WordPress is complex. It uses ACLs. As a trade for the complexity, you end up with a WordPress environment that,
- Segregates different clients from each other.
- Minimizes damage if WordPress itself is hacked.
- Has Two Factor Authentication.
- Scales quickly.
- Is Easy to backup and recover.
As such, this article is not for faint of heart. Though all the steps here show how to do the work, you might want to ensure a good understanding basic Unix permissions and then read the ACLs tutorial to understand how everything is working.
Introduction
The key technology to securing WordPress to segregate is ACLs.
(Put Table of Structure Here)
Install PHP
Install Packages
sudo apt-get install php5
As of Ubuntu 12 (an maybe even earlier), the installer will automatically restart Apache2 for you.
Verify
Quickly verify that everything works by creating a php info file with your favourite editor,
sudo vi /var/www/info.php # On public site do not use such an obvious file name
Put in the following contents,
<?php phpinfo(); ?>
Save the file and browser to file using a browser. You can use either the IP Address or the valid Domain Name. For example, http://173.194.75.94/info.php or http://www.dailyplanet.com/info.php which should show a purple and grey PHP informational screen.
Once you know things are good, delete the info.php file.
Secure PHP
This security posture contains php to run only within the specified php folders.
Create Web Folder
This will become a snippet that is part of the Bonsai Framework Apache Virtual Hosting directory structure,
If you have not already, as part of the Apache Virtual Hosting tutorials, setup the web folder,
cd ~ mkdir ./web/ sudo chown -R serveradmin:staff ./web/ sudo chmod -R u+rwX,g+r-w+X,o-r-w+X ./web/ sudo mv ./web/ /opt/
The permissions basically allow serveradmin full access, staff read and directory access, others only directory browsing access. That means a normal user will be able to cd into /opt/web/ but not have access to do a directory listing on anything. This is necessary to grant ALL clients access to the directory tree. Further down, we will restrict further where other will have no access at all.
As part of the advanced ACL approach to Apache Virtual Hosting (still to write that topic and then link to this article),
cd /opt/ sudo setfacl -Rm g:www-data:r-X ./web/ getfacl --access ./web/ | sudo setfacl -d -RM - ./web/
The ACL grants the Apache process running as www-data read and directory browsing access to the web folder.
Create php Support Folders
PHP requires advanced access to specific folders,
cd /opt/web sudo mkdir php sudo chown -R serveradmin:staff ./php/ sudo chmod -R u+rwX,g+r-w+X,o-r-w+X ./php/
Notice the ACLs step is skipped. The php folder will inherit the permissions of allowing www-data access from the default ACLs defined in the web folder.
Next create the php support direcotries,
cd /opt/web/php sudo mkdir tmp logs sudo chown serveradmin:staff tmp logs sudo chmod -R u+rwX,g+r-w+X,o-rwx ./tmp/ ./logs/ sudo setfacl -Rm g:www-data:rwX ./tmp/ ./logs/ getfacl --access ./tmp/ | sudo setfacl -d -RM - ./tmp/ ./logs/
At least initially, these directories should not grant permission to other users.
php.ini for WordPress
Edit php.ini to only allow execution of php scripts in specific directories.
sudo vi /etc/php5/apache2/php.ini
Restrict the Execution of PHP to a Specific Folder
Search for the open_basedir line and modify to include the directories setup for WordPress
; open_basedir, if set, limits all file operations to the defined directory ; and below. This directive makes most sense if used in a per-directory ; or per-virtualhost web server configuration file. This directive is ; *NOT* affected by whether Safe Mode is turned On or Off. ; http://php.net/open-basedir open_basedir = /opt/web/php
This helps minimizes the amount of damage that can be done in the event that the system is compromised to the specified directory.
Select Temporary Folder
Because open_basedir has been set, WordPress no longer has access to the general temporary folder it expects which is required for certain operations (for example to upload plugins through the Administrator web interface).
Modify php.ini further by modifying the upload_tmp_dir line,
; Temporary directory for HTTP uploaded files (will use system default if not ; specified). ; http://php.net/upload-tmp-dir upload_tmp_dir = /opt/web/php/tmp/
Make Changes Take Effect
Restart Apache for the changes to take effect,
sudo service apache2 restart
You will now find that php scripts will only run in the designated directories specified in php.ini.
Install MySQL
sudo apt-get install mysql-server
For the root administration database password, use the standard password algorithm based on the server name.
Connect PHP to MySQL
Install the necessary libraries so that PHP will be able to connect to MySQL.
sudo apt-get install php5-mysql
Configure MySQL
Secure MySQL
As a staff user run the Secure Installation script included with MySQL,
mysql_secure_installation
The prompts are very straightforward. Except for "Change the Root password?", answer yes to all prompts by hitting Enter,
For now that's it to securing MySQL.
Connect
Connect into MySQL,
mysql -u root -p
The password to use is the password set during the MySQL install. If everything goes well you will be in the MySQL shell,
Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 36 Server version: 5.5.24-0ubuntu0.12.04.1 (Ubuntu) Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
The remainder of this section happens inside of the mysql shell.
Create the WordPress Database and Accounts in MySQL
List the databases to makes sure what you want to create does not already exists,
SHOW DATABASES; mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | +--------------------+ 3 rows in set (0.16 sec)
In our case we should have nothing other than the three default databases.
Enter the following MySQL commands,
CREATE DATABASE wpdailyplanetdb; GRANT ALL PRIVILEGES ON wpdailyplanetdb.* TO 'wpdailyplanetdbuser'@'localhost' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;
Adjust the variables for your application.
wpdailyplanetdb - Name of the database for the WordPress application instance. We use the domain name of the website.
wpdailyplanetdbuser - User account for accessing the database.
localhost - Address of the database server. In this example, the database is on the same server so use localhost.
password - Change to password using algorithm based on name of the primary website domain, in this case dailyplanet.
Database Admins will not like granting all privileges. After the initial setup is done we will restrict to more minimal privileges.
Exit MySQL Shell
Exit the MySQL shell,
EXIT
Setup WordPress
WordPress is incredibly easy to setup and there are many shorter tutorials than this.
This tutorial, takes a more secure approach
WordPress out of the box can be very quickly broken into. In fact, I personally go so far as to keep the Ubuntu firewall up with port 80 or 443 closed until WordPress is completely hardened. When the setup steps required using the browser, I use ssh tunnelling to access 80 securely. A writeup of using ssh tunnelling should be added to the Bonsai Framework and linked or included here.
Install WordPress
Create Virtual Host Folder
Create and secure the virtual host root folder that will contain both static content and WordPress,
cd ~ mkdir -p ./dailyplanet.com/www/
Using a staff account, download and install WordPress,
cd /~/dailyplanet.com/ wget http://wordpress.org/latest.tar.gz tar -xvpf latest.tar.gz mv ./wordpress/ ./blog/ # We do not need to make the technology obvious.
Setup File Permissions
Here are how the file permissions will eventually look,
Directory | Purpose | user:group:other | ACL Group | Default ACL Group | Tech Notes |
---|---|---|---|---|---|
dailyplanet.com/ | Top level folder for the client. Only the client has read and write access. | rwX:r-X:--- | www-data=r-X staff=r-X wgdailyplanet=r-X | Same as ACL Group | |
dailyplanet.com/www/ | Inherited | Inherited | Same as ACL Group with following exceptions, wgdailyplanet=rwX | ||
dailyplanet.com/blog/ | Inherited- | Inherited | Same as ACL Group | ||
dailyplanet.com/blog/wp-content/plugins/ | Plugins are dropped in here. | Inherited | Same as ACL Group with following exceptions, wgdailyplanet=rwX | The plugin can be installed through WordPress Administration two ways,
Option 2 does not work out of the box unless the owner of the directory is www-data. me, but I suspect it is due to having set in php open_basedir which does not have a writable temporary folder. This can be resolved by specifying upload_tmp_dir which must reside in open_basedir and also where www-data has rwx access. |
To setup these permissions with ACLs as the staff user,
cd /opt/web/php/ sudo setfacl -Rm g:www-date:rwX ./tmp/ getfacl --access ./tmp/ | sudo setfacl -d -RM - ./php/tmp/ sudo setfacl -Rm g:www-date:rwX ./logs/ getfacl --access ./logs/ | sudo setfacl -d -RM - ./php/logs/ sudo setfacl -Rm g:www-date:rwX ./wp-content/ getfacl --access ./wp-content/ | sudo setfacl -d -RM - ./wp-content/
The first set of ACLs ensure that php can get proper access. The last ACL ensures that WordPress can install plugins and themes.
Configure WordPress
Create Config File for Database Access
Launch a browser and hit the WordPress setup page for your machine at http:// dailyplanet.com/blog/wp-admin/install.php and you will be prompted to create a configuration file.
Click the button, "Create a Configuration File".
The next prompt reminds you of all the critical information you will need. The Bonsai Framework takes a high security posture, so the automatic file creation should not work. Click the "Let's go!" button.
Enter the required information and click "Submit",
Field | Value | Comment |
---|---|---|
Database Name | wpdailyplanetdb | |
User Name | wpdailyplanetdbuser | |
Password | This is the application password set during the wpdailyplanetdb database creation step. | |
Database Host | localhost | Address of the database server. In this example, the database is on the same server so use localhost. |
Table Prefix | bf_ | The Bonsai Framework approach is to not share a single database instance. As such it is not technically necessary to change the table prefix. However, given the architecture of WordPress and popularity it is recommended to change the prefix to something other than wp_ to make the system less susceptible to attacks. |
It is expected that you will receive a message that WordPress can not write the wp-config file,
Sorry, but I can't write the wp-config.php file.
You can create the wp-config.php manually and paste the following text into it.
Copy the generated wp-config.php file.
Some shortcuts to ensure you get it all fast,
- Click inside of the text box
- Use the keyboard combo CTRL-A (to select all)
- CTRL-C (to copy)
Go to your shell, load your favourite editor and paste the contents of the wp-config.php file,
vi /opt/www.dailyplanet.com/www/blog/wp-config.php
Also while you are there add the following to the bottom of the file,
# WordPress determines write access when it finds a match between the Web Server ID and the directory being written too. # This approach does not work with ACLs. During manual file upload the failure results in a prompt for FTP login information. # Override the write check by forcing a direct write define('FS_METHOD', 'direct');
This is to allow the ACL approach. Yes we could make the owner of the wp-content directory www-data, but that breaks the general approach by the Bonsai Framework.
I'm not convinced this is the best resolution and it seems to me like a hack. File a bug with WordPress to get ACL information instead. In the interim, consider breaking the Bonsai Framework approach and just chowning www-data as the user with rwX.
Once you have saved the file, go back to your browser and click "Run the install".
Enter Site Information
Finally enter the site information,
Field | Value | Comment |
---|---|---|
Site Title | dailyplanet | We like to reference our domain name. |
Username | tempadmin | You probably do not want to use the default admin for username. WordPress (as of Sep 2012) out of the box, has no facilities to stop dictionary attacks against the administration system. Admin will be the first username guessed by automated attacks. Because the username put here will show up in the default site generated, this will be a temporary administrator account. |
Password | As mentioned, WordPress has no facilities to stop dictionary attacks. On top of that, the default setup exposes your administrator account name on the Internet. Choose a very very long and complex password. (Anyone know of a good site that shows how quickly an entered password would be broken with a dictionary attack, put the link here) | |
Your E-mail | Whatever email is chosen here, it will not be the final one used by the real administrator account. Keeping in mind that WordPress does not allow duplicate emails, in this example, the administrator will use a personal email and then use a proper email account when the real administrator account is created. | |
Privacy | This depends on the purpose of your website. Unless this is a private site that should not show up on Google, leave it checked. |
Click, "Install WordPress" which should result in a success screen. At this point you are actually done the setup. Do not click "Log In".
Customize WordPress
At this point WordPress is already working. There are two urls to take note of,
URL | Area | Purpose |
---|---|---|
http://www.dailyplanet.com/blog/ | Public | You can hit this url right now and see a default working site. This url is where your users will enter. |
http://www.dailyplanet.com/blog/wp-admin/ | Administration | This url results from clicking the "Log In" button after the WordPress install is complete. It can also be accessed through the Public homepage by click "Log In" located at the bottom right under "META". The Administration area allows the customization and configuration of WordPress. Also, once logged into the administration, if you browse to the public area, you will see additional buttons and options to create posts and edit the website contents. |
If you have the Install WordPress Success Screen still up, click "Log In" will take you to the Word Press Administration url or use the url in the table above.
Minimal Security - Block Login Attacks
Plugin | Description | Review |
---|---|---|
Google Authenticator | The Google Authenticator plugin for WordPress gives you two-factor authentication using the Google Authenticator app for Android/iPhone/Blackberry. If you are security aware, you may already have the Google Authenticator app installed on your smartphone, using it for two-factor authentication on your Gmail or Google Apps account. The two-factor authentication requirement can be enabled on a per-user basis. You could enable it for your administrator account, but log in as usual with less privileged accounts. If You need to maintain your blog using an Android/iPhone app, or any other software using the XMLRPC interface, you can enable the App password feature in this plugin, but please note that enabling the App password feature will make your blog less secure. | Very good plugin. Tricky part is making sure time is synced with same time servers across the phone and server. For example, my iphone was off by 2 minutes because it was set manually to Toronto. Best thing to do is turn on the 4 minute drift allowance. |
Duo Two-Factor Authentication | This plugin enables Duo Security's two-factor authentication for WordPress logins. Duo provides simple two-factor authentication as a service via:
This plugins allows a WordPress administrator to quickly add strong two-factor authentication to any WordPress instance without setting up user accounts, directory synchronization, servers, or hardware. | Free signup but it looks like only 1000 transactions for the life of the account. Looks very professional. |
BAW More Secure Login | Grid Cards | |
Login Security Solution | Most useful feature I find when used with Google Authenticator is that it blocks user for x number of minutes progressively more as more attempts are tried. Also blocks by cookie and ip. |
Should have link to how to ssh in to disable plugins if they misbehave.
Set Up Users
The default user created is an administrator and has more privileges than necessary. The very first step is to create users with specific roles provided by WordPress. The roles are outlined below in order of most privileges to least.
Role | Description | UserName |
---|---|---|
Administrator | Administrators have access to all the administration features. | setupadmin |
Editor | Editors can publish posts, manage posts as well as manage other people’s posts, etc. | perrywhite |
Author | Authors can publish and manage their own posts, and are able to upload files. | clarkkent, loislane |
Contributor | Contributors can write and manage their posts but not publish posts or upload media files. | jimmyolsen |
Subscriber | Subscribers can read comments/comment/receive newsletters, etc. but cannot create regular site content. | lexluthor |
(explain why we do not use the first admin account we created) Creat the real administrator account,
Field | Value | Comment |
---|---|---|
Site Title | dailyplanet | We like to reference our domain name. |
Username | setupadmin | WordPress (as of Sep 2012) out of the box, has no facilities to stop dictionary attacks against the administration system. So pick something not that obvious. The example here is very obvious so don't use it. Consider using your server name. |
Password | As mentioned, WordPress has no facilities to stop dictionary attacks. On top of that, the default setup exposes your administrator account name on the Internet. Choose a very very long and complex password. (Anyone know of a good site that shows how quickly an entered password would be broken with a dictionary attack, put the link here) | |
Your E-mail | admin@bonsaiframework.com | If there is more than one administrator, you should have a general support email box that only administrators have access to. This email address will be used for password recovery purposes. |
...
Past this point is not yet organized or complete.
Lock Down WordPress
WordPress and PHP simply due to the model is inherently insecure when compared to more Enterprise solutions.
As such the Bonsai Framework takes an administrator approach to managing and securing WordPress. It is strongly recommended to not use a co-hosting model for multiple clients that require privacy. This is especially problematic if clients are granted shell access. It becomes very complex to protect one client from gaining access to another client's WordPress data.
WordPress updates through the built in admin interface will fail unless the restrictions are relaxed. With this security approach, privileges must be temporarily be granted as part of the upgrade process.
Restrict WordPress Database Account
As part of good application security, the WordPress database account should only be granted minimal privileges.
Connect into MySQL,
mysql -u root -p
Enter the following MySQL commands,
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'wpdailyplanetuser'@'localhost'; GRANT SELECT, INSERT, UPDATE ON wpdailyplanetdb.* TO 'wpdailyplanetuser'@'localhost' IDENTIFIED BY 'password'; FLUSH PRIVILEGES; EXIT
Adjust the variables for your application.
wpdailyplanetdb - Name of the database for the WordPress application instance. We use the domain name of the website.
wpdailyplanetuser - User account for accessing the database.
localhost - Address of the database server. In this example, the database is on the same server so use localhost.
password - Change to password using algorithm based on name of the website domain, in this case dailyplanet.
Verify the changes took effect,
SHOW GRANTS FOR 'wpdailyplanetuser'@'localhost';
File Permissions
I need to work out what folders explicitly need permissions to perform uploads and plugin updates. Still to finalize placement of this section.
Fom the WordPress article Hardening WordPress we might want to take the approach of creating accounts for select developers or release managers.
wp-config.php - holds the database password and should be locked down (it is thanks to the ACLs)
Covered on the Ubuntu WordPress guide, for automatic updates to occur, the folder and all its files and sub-folders must be owned by www-data with write access. I'm not sure we will take this approach. I think I'd rather update manually.
...
Writing Next Topics
- Repeat for the second instance.
FAQ
Why do some of the php5 installations say to use install libapache2-mod-php5?
No need, it is included with the php5 package.
What is the difference between the php5 and libapache2-mod-php5 packages?
Nothing I can see. It just looks like php5 is an overarching package name.
References
Setup
Ubuntu Server Documentation - https://help.ubuntu.com/12.04/serverguide/php5.html
Security
Has some ok details around suPHP - https://help.ubuntu.com/community/ApacheMySQLPHP#Installing_MYSQL_with_PHP_5
Some good notes on securing PHP from Symantec - http://www.symantec.com/connect/articles/securing-php-step-step
Start some good security practices for WordPress - http://www.howtospoter.com/web-20/wordpress/triple-p-of-total-wordpress-security
Wordpress discussion on permissions, based their recomendations for suPHP the community does not really understand permissions - http://codex.wordpress.org/Changing_File_Permissions
This restricts the php process to specific directories - http://help.godaddy.com/article/1616
At least by looks this looks like it may be a good guide to securing wordpress - http://wpsecure.net/basics/
Wordpress' official article to getting started after setup - http://codex.wordpress.org/First_Steps_With_WordPress
Article which resolved manual upload issue - http://www.charleshooper.net/blog/wordpress-auto-upgrade-and-dumb-permissions/
Determine if this actually increases security - http://www.suphp.org/Home.html. suPHP and LiteSpeed make the most sense for shared hosting.
This article indicates that suphp is slow as it makes php run as a cgi. Instead a poster recommended using what is available with mod_php - http://serverfault.com/questions/279938/should-i-use-suphp-or-mod-php-for-shared-hosting. Along this thread another poster recommends, http://mpm-itk.sesse.net/ which allows vhosts to be run under different uid and gid.
A great discussion on using permissions, same conclusion I was coming to using www-data group - http://unix.stackexchange.com/questions/30879/what-user-should-apache-and-php-be-running-as-what-permissions-should-var-www
Probably the most complete but also complex solutions is to use ACLs - http://serverfault.com/questions/339948/user-permissions-for-both-apache-and-local-user/357977
group:www-data:r-x