Additional WordPress Hardening for Subfolder Instances

WordPress is content management system (CMS) software that is made to be simple for people to use. It’s very popular is and probably best for blogs, but is used for all kinds of websites—from hobbyist to professional. Even ChadSpace uses WordPress! (At least until ChadPress is finished, which is going to be totally awesome.)

Unfortunately, the above mentioned qualities—that is, “software” and “simple”—do not always complement each other so well. Sometimes, this particular combination of development priorities creates opportunities for end-users to screw themselves. This is especially true whenever necessarily complex tasks are attempted to be made simpler than they really are. Unsurprisingly, WordPress sites, which are “simple” to set up, are highly susceptible to attacks, as detailed in many articles online icon-external-link-12x12 icon-search-12x12 .

One of the scariest things about WordPress is that there exists a file called wp-config.php that contains a username and password for the associated SQL database, and it is located in the servable space of a website. The password is given in plaintext—that is, unhashed and unencrypted—and reads clear as day for anyone with access. This is utterly absurd, and serves as a huge red flag for any self-respecting technologist.

Assuming your server is running PHP, and that it is configured correctly, anyone attempting to view the contents of wp-config.php in their web browser will be treated to a blank document. However, technology frequently sucks icon-external-link-12x12 and a routine system update procedure can kill PHP but leave Apache running, exposing a WordPress site’s database password to the entire world.

There are additional precautions that can and should be taken to keep the data in this file off limits. If you are hosting your site with Apache Web Server, an additional setting added to the main .htaccess configuration file can deny all external access. This works, but still isn’t good enough.

WordPress also allows end-users to move the wp-config.php file up one directory, which is presumably outside of a website’s servable space, and the software will still find the file in its new and more protected location without any additional configuration. This is the right idea, but doesn’t work for WordPress sites that are installed in the subfolder of a domain—like ChadSpace, for instance.

So don’t be a shithead: protect the database password for realsies. This can be done by making some rather quick changes to the wp-config.php file.

Step 01) Create four new files, all of which are located two directories up—or more, if necessary—from where WordPress is located (adjacent to the public_html folder if you are using a conventional Apache setup):

<sitename>-wordpress-db-config.php
<sitename>-wordpress-env-config.php
<sitename>-wordpress-plugins-config.php
<sitename>-wordpress-keys-config.php

Where <sitename> is… yeah, nevermind. ChadSpace is hosted on the unwisdom.org domain and so its files—chadspace-wordpress-db-config.php, chadspace-wordpress-env-config.php, etc.—are adjacent to the public_html folder that represents Apache’s servable space for unwisdom.org.

Step 02) Start gutting your existing wp-config.php file and put the code in the relevant files that were just created. For ChadSpace, this looks like the following:

chadspace-wordpress-db-config.php

<?php
// ** MySQL settings - You can get this info from your web host ** //

/** The name of the database for WordPress */
define('DB_NAME', 'chadspace');

/** MySQL database username */
define('DB_USER', 'chadspace');

/** MySQL database password */
define('DB_PASSWORD', 'superSECRETdatabasePASSWORDthatNOBODYknows!!!');

/** MySQL hostname */
define('DB_HOST', 'localhost');

/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');

/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');

chadspace-wordpress-env-config.php

<?php
/** The maximum file upload size. */
define('WP_MEMORY_LIMIT', '64M');

/**
 * WordPress Database Table prefix.
 *
 * You can have multiple installations in one database if you give each a unique
 * prefix. Only numbers, letters, and underscores please!
 */
$table_prefix  = 'wp_';

/**
 * WordPress Localized Language, defaults to English.
 *
 * Change this to localize WordPress. A corresponding MO file for the chosen
 * language must be installed to wp-content/languages. For example, install
 * de_DE.mo to wp-content/languages and set WPLANG to 'de_DE' to enable German
 * language support.
 */
define('WPLANG', '');

/**
 * For developers: WordPress debugging mode.
 *
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
 */
define('WP_DEBUG', false);

/** Override default site URL values in database (for site migration) */
define('WP_HOME','http://unwisdom.org/chadspace/');
define('WP_SITEURL','http://unwisdom.org/chadspace/');

chadspace-wordpress-plugins-config.php

<?php
//define('WP_CACHE', true); //Added by WP-Cache Manager
define( 'WPCACHEHOME', '/var/www/unwisdom.org/public_html/chadspace/wp-content/plugins/wp-super-cache/' ); //Added by WP-Cache Manager

chadspace-wordpress-keys-config.php

<?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',         'c_xztwa)O^A8lWeyGVJC(J!HHE8h(Rj\`e$i9fXP!r_RO|6fz=@r!G@z1P9ufDoxzb:VEF');
define('SECURE_AUTH_KEY',  '7SbkH2A1NeH-6XNgu;a^PwYvE*VrYmgvy5Z90$#eR~U|9tHpkUbT4_?$4JGj6Or@Gg7RHW|df');
define('LOGGED_IN_KEY',    '-F/mR?mj_(uOb$uJp@6^WvNN?aKF44J!p~:eUrM13zJ0G/Zr0T\`d~af3vft)anW2PB!ZY');
define('NONCE_KEY',        '_snMEr2=KxSlQ;m~^gVFfTea~p9m4hF|$k5x58z?eLIsOpZ_MGn1kHw(b^9rStm#eu0');
define('AUTH_SALT',        'jL\`PEQtP$0G3hJEfh;ai(YO5=C8hww$bC3P=^eM1db\`u|\`c*xZ7-8bS7tQ5S(3a/lr');
define('SECURE_AUTH_SALT', '!J=_ZYYL5mW|VUQ@SNy#eyb0fI\`Nu|CZ7oG-ql4c~tO!0sS6H5ZQFC45-(s0Jjx\`7viHt|^lrO0|w');
define('LOGGED_IN_SALT',   'HtW(RyWtVrgQ$_?Fdy|ej*pEz@y52jtr|moTACHc-XWYP-OGC@UQ59!#G-3db5=M#T:2;jeED)IITa((Ts');
define('NONCE_SALT',       '(Phh\`)~QI@mzx*/n:F?IgB/XnZNjarXTUXj_1T6e2^CgnP^YvjMs5iVKe!~QpblLE-');
/**#@-*/

Step 03) Update and slightly restructure your wp-config.php file. This involves moving the definition for ABSPATH to be the first chunk of code that is executed; executing the four newly created files with include function invocations; and leaving the require_once function call for the wp-settings.php file as the final executed line.

wp-config.php

<?php
/**
 * The base configurations of the WordPress.
 *
 * This file has the following configurations: MySQL settings, Table Prefix,
 * Secret Keys, WordPress Language, and ABSPATH. You can find more information
 * by visiting {@link http://codex.wordpress.org/Editing_wp-config.php Editing
 * wp-config.php} Codex page. You can get the MySQL settings from your web host.
 *
 * This file is used by the wp-config.php creation script during the
 * installation. You don't have to use the web site, you can just copy this file
 * to "wp-config.php" and fill in the values.
 *
 * @package WordPress
 */

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
        define('ABSPATH', dirname(__FILE__) . '/');

include(ABSPATH . '/' . '../../chadspace-wordpress-db-config.php');
include(ABSPATH . '/' . '../../chadspace-wordpress-plugins-config.php');
include(ABSPATH . '/' . '../../chadspace-wordpress-env-config.php');
include(ABSPATH . '/' . '../../chadspace-wordpress-keys-config.php');

/* That's all, stop editing! Happy blogging. */

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

Configuring wp-config.php in this way does a couple of important things: it ensures that the database password can never be accessed through web server routines, and it abstracts away icon-external-link-12x12 the directory structure of your web server. This means that if someone is somehow able to view the contents of this file, it will not betray any sensitive information about the host server and how it is configured.

All that said, even if a WordPress database password is exposed, it doesn’t guarantee that an attacker will be able to gain access to the database server. But is it really okay that so many WordPress webmasters are taking this unnecessary risk? Compromised computer security is just a game of probability: out of one million WordPress sites, how many experience server trouble from time to time or don’t understand how to set file permissions correctly? Many of these people inadvertently leave their database passwords exposed to the world. Of these, how many are using this same password somewhere else, like the associated WordPress admin account… or an email account… or an Xbox Live account… or an online banking account?