Based on last weeks work, we will try to improve your WordPress security.
Errata: please have a look at the updated version of last week tutorial (or the diff on Github).
You should already have:
If you do not have this setup yet, please ask as soon as you can so we work that out. The next lecture should last only an hour, the second hour being dedicated to live coding examples based on your questions, so please come and ask.
While this is a fairly simple WordPress setup with just a few features, it covers the main ways to extend WordPress and should be enough to introduce security basics.
Security will have to be set at different levels that we will details below:
Because that is a lot to think about, and that we are not a security class, we will only look at the concepts here and how to cover the most usual vulnerabilities.
Have a look at your WordPress configuration file first (wp-config.php
). Your configuration is either moved to another file and included in wp-config.php
or your configuration is setup in this file directly. In this practical we will consider that your configuration is in the wp-config.php
file, ask if you need help with adapting your code.
The first lines of your file should look like the sample below (extracted from wp-config-sample.php
, a sample configuration provided in WordPress core):
<?php
/**#@+
* Authentication Unique Keys and Salts.
*
* Change these to different unique phrases!
* You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
* You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
*
* @since 2.6.0
*/
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
define('LOGGED_IN_KEY', 'put your unique phrase here');
define('NONCE_KEY', 'put your unique phrase here');
define('AUTH_SALT', 'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT', 'put your unique phrase here');
define('NONCE_SALT', 'put your unique phrase here');
If you have these default keys, you should changed them with a set generated by using the WordPress API. This will invalidate any session you have, and therefore disconnect evey logged in users.
If you do not know what a salt is, please read this article: ttmm.io: WordPress password hashing. In a nutshell, a salt is a string used to lengthen plain text strings before hashing, and therefore have a more complex string stored than the basic string used as password for instance.
The second step in order to secure your WordPress login page would be setting up an ssl certificate and move all the connexions to https. Because we can not try that here, just know you may use Virtualbox to setup your own SSL certificate on a Linux virtualised system with LAMP installed on it, or you could use free offers from the Github Student pack. Your hosting provider will generally offer an ssl option on your webhosting package anyway.
Two other issues are remaining and can be solved with adding plugins: using a Two Factor Authentication and limiting the number of login attemps to avoid brute force attacks. You can try to install Limit Login Attempts and Google authenticator. If you have a smartphone with Google Authenticator installed, please try to setup your account (in your user profile).
As discussed in the lecture, you should also make sure there are no users with usernames such as admin
or administrator
.
While the first part was about testing tools provided by developers to secure your users access, we are now going to make a few changes to last week plugin in order to secure it from attacks.
First, we need to make sure the user input has been limited and validated to the type of content you want to have. When the user is submitting the form, the information is transmitted over to the webserver and processed by the PHP interpreter.
You should have the following code (or something similar) in your plugin main file:
function napier_newsletter() {
if (isset($_POST['napier_newsletter_submit'])) {
$emailAddress = filter_input(INPUT_POST, 'napier_newsletter_email');
form_validation($emailAddress);
form_processing($emailAddress);
}
form_rendering();
}
As you can see, the code here is filtering the email address. In some tutorials you may find online, developers are accessing parameters from the $_POST
global array, but using the filter_input we already make sure only authorised characters are used (others will be sanitised or removed).
As an experience, remove this protection and access the global post array directly:
$emailAddress = filter_input(INPUT_POST, 'napier_newsletter_email');
$emailAddress = $_POST['napier_newsletter_email'];
if (!is_email($emailAddress)) {
$napier_newsletter_errors->add('email_invalid', 'Email is not valid');
}
$email_exists = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}napier_newsletter");
if ($email_exists > 0) {
$napier_newsletter_errors->add('email', 'Email address already registered');
}
/*if (!is_email($emailAddress)) {
$napier_newsletter_errors->add('email_invalid', 'Email is not valid');
}
$email_exists = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}napier_newsletter");
if ($email_exists > 0) {
$napier_newsletter_errors->add('email', 'Email address already registered');
}*/
<input type="email" name="napier_newsletter_email" value="<?= (isset($_POST["napier_newsletter_email"]) ? esc_attr($_POST["napier_newsletter_email"]) : '') ?>" size="255">
<input type="text" name="napier_newsletter_email" value="<?= (isset($_POST["napier_newsletter_email"]) ? esc_attr($_POST["napier_newsletter_email"]) : '') ?>" size="255">
Now, you can try to pass along different strings in the input field and see how the system behave.
<script>alert('test');</script>
If you are using Google Chrome, use the developer tools to see the errors, as Chrome does not allow XSS (The XSS Auditor refused to execute a script in 'http://localhost:9000/' because its source code was found within the request. The auditor was enabled as the server sent neither an 'X-XSS-Protection' nor 'Content-Security-Policy' header.
). I haven't verified the behavious on other web browsers, but you should either have a javascript alert or some kind of error in the developer tools.
<style>body { height: 100vh; width: 100vh; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999; background: silver; }</style>
This should only alter your style.
Use your imagination along with the resources found in the lecture and try other attacks. You should see that WordPress already set a few defences for you. Then set your protection back and try to improve it as you can.
As an other example, we will try to protect the form to be filled from outside of your website (disallow Cross Site Request Forgery). Outside of your project (anywhere on your computer), create an html file (let's call it test.html
):
<form action="http://localhost:9000/" method="post">
<p>
<label>
Email address:
<input type="text" name="napier_newsletter_email" size="255">
</label>
</p>
<p>
<input type="submit" name="napier_newsletter_submit" value="Register"/>
</p>
</form>
Use your website url as action, as we have the plugin form on every page (through the theme), any url on your website may work. Open this file in your web browser and submit the form with a valid email address that hasn't been already registered.
You should have been able to register from outside the website. In order to avoid this behaviour, we will implement some sort of CSRF protection.
Please read WordPress front end security csrf and nonces and try to implement this security on your plugin.
Because you can not change rights on the university computers, we will not cover this in details. Just keep in mind that you can have filesystems and databases with read-only mode or restricted access, and use it accordingly.