Important Security Fix for WordPress
The other day, my server crashed and Perishable Press was unable to connect to the MySQL database. Normally, when WordPress encounters a database error, it delivers a specific error message similar to the following:
Default database-error message
This customizable database error message explains the situation to visitors and circumvents any malicious activity involving exposed scripts, PHP errors, and other issues related to unexpected database issues.
That sounds nice, but there is a problem
The problem that I painfully discovered when my server crashed is that WordPress does not always display the default page for all database-related issues. Apparently, if the database is missing entirely, WordPress assumes that it has not yet been installed and loads the Installation Page:
WordPress Installation Page
Yikes! This is exactly what happened when my server crashed, MySQL was unavailable, and the WordPress Installation Page was displayed to over 100 visitors while I scrambled to resolve the issue.
During the event, there were several attempts to assume control of my site through the Installation Page. Fortunately, I was working on the site (via FTP, cPanel, phpMyAdmin, and so on) during the attacks, and was able to terminate an inevitable hostile takeover.
“john@greatCampingTrips.com”, I’m staring at you here.
It happened to me, and it could happen to you
To me, this scenario represents an enormous security risk for all currently available versions of WordPress (up to 2.8 at the time of this writing). If WordPress serves up the Installation Page the next time your database goes down, anyone could easily gain full control of your entire server. By simply entering an email address and specifying a blog title, an attacker would administratively re-install WordPress and receive the following message:
WordPress installation-success message
Ouch! This is no good. I was fortunate enough to have been there during the incident, however it could have happened while I was away from the computer. What if I had been asleep, at work, or on vacation? Trust me, it doesn’t take long for a savvy attacker to annihilate an entire server, and the damage may be irreversible.
A temporary solution, until WordPress does it better
After restoring full functionality to my site, deleting multiple “Hello world!” posts and “About” pages, and removing the newly added Administrator, it was time to prevent this situation from happening again. The easiest way to do this involves deleting, blocking, or modifying the wp-admin/install.php
file, which contains the script that generates the Installation Page.
Until WordPress incorporates a better solution, I recommend one of the following three fixes:
Fix #1: Just nuke it
Simply delete the wp-admin/install.php
file entirely. It is not needed after installation.
Fix #2: HTAccess to the rescue
Place the following slice of HTAccess into your site’s web-accessible root directory to prevent access to your install.php
file:
# PROTECT install.php
<Files install.php>
Order Allow,Deny
Deny from all
Satisfy all
</Files>
Fix #3: Replace it with something safe and useful
Replace the insecure version of the file with something secure and informative by following these quick steps:
- Rename the original
install.php
to something like, “install_DISABLED.php” or whatever. - Create a new file named “
install.php
” and add the following code:
<?php // install.php replacement page: https://perishablepress.com/press/2009/05/05/important-security-fix-for-wordpress/ ?>
<?php header("HTTP/1.1 503 Service Temporarily Unavailable"); ?>
<?php header("Status 503 Service Temporarily Unavailable"); ?>
<?php header("Retry-After 3600"); // 60 minutes ?>
<?php mail("your@email.com", "Database Error", "There is a problem with teh database!"); ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Error Establishing Database Connection</title>
</head>
<body>
<img src="images/wordpress-logo.png" />
<h1>Error Establishing Database Connection</h1>
<p>We are currently experiencing database issues. Please check back shortly. Thank you.</p>
</body>
</html>
Once uploaded to your server, this new install.php
file will prevent any malicious behavior by serving up a static web page. Now, instead of showing the Installation Page when your database is unavailable, WordPress will display the following message:
WordPress installation replacement page
In addition to displaying this information to your visitors, the Installation Replacement Page also performs the following actions:
- Communicates a
503 (Service Temporarily Unavailable)
status code to clients and search engines - Instructs clients and search engines to return after 60 minutes (configurable)
- Sends an email informing you of the situation so that you may take action
To use the Replacement Page, don’t forget to specify an email address in the fourth line. You may also change other variables, such as the time duration, email subject, or email message. If you need any help with these variables, please leave a comment. I have also made the Installation Replacement Page available for easy download:
Live long and prosper
Until the developers at WordPress implement a cleaner, more permanent solution for this issue, I highly recommend applying one of the solutions provided in this article. There is no reason to keep the original install.php
file after you have installed WordPress, so feel free to delete, modify, or block it. Trust me, dealing with the trauma of watching your site get hijacked by some unscrupulous cracker whore just ain’t worth the pain.
Update
MustLive provides more insight into the install.php vulnerability via this comment and this article: Attack on Abuse of Functionality in WordPress. Many thanks to MustLive for his work on this security issue!
86 responses to “Important Security Fix for WordPress”
@mccormicky, sounds like you’re having strange MySQL issues. I just checked and your install.php file says “Already Installed”.
The test performed by install.php is very specific. It tries to load the siteurl option from the wp_options table. If that is not found, it presumes that WordPress has not been installed. If it cannot talk to MySQL at all, then you get a different error.
So for install.php to be a security risk, MySQL needs to be up and responding, but siteurl is not found in wp_options.
If you’re changing your MySQL username and password, that sounds like you’re having permissions issues. I’d suggest regular backups until you resolve the issues. You might try contacting your hosting company to see if they’re having MySQL problems.
Best of luck with your issues.
@mccormicky: On a default installation of WordPress, the install.php file may be accessed directly. If the options table is available, the message will read something like:
If the database is unavailable, as Callum points out in the previous comment, the wp-includes/wp-db.php file generates an error message such as:
This message may be overridden with a custom database message by creating a file named “db-error.php” and placing it in the wp-content directory. This file is just like any other PHP file and may be configured according to your needs (error logging scripts, email alerts, whatever).
In any case, if your database-related issues are complex, you may want to simply start over with a fresh install and troubleshoot from the ground up. If the problems persist on an out-of-the-box installation of WordPress, your web host definitely should be informed of the issue.
If starting over from scratch doesn’t work for you, you may benefit from setting up a new test site where a default installation may be tested. A new setup will eliminate many of the variables that may be confounding the issue, and enable you to retain full control of the testing process from the ground up.
I hope this helps, mccormicky — let me know how it goes.
@Callum: Thanks for the reply. I’m glad that you’ve finally resolved the difference in opinion between you and Jeff. Of course, I do respect the opinions from both sides – a little constructive input from either side makes a good discussion perfect.
@mccormickiy: I’m wondering if there’s a problem wiht your database. There were several instances where my server was still online but the database was uncontactable, resulting in the installation page in full view of others as well. Sometimes it gives me the “Error in database connection” message, or sometimes I get totally blank pages on my WP blog. If you’re looking to setup a WP blog for testing purposes like what Jeff has suggested, instead of burdening your host with another WP installation (and if you’re lazy to wait for FTP uploads like me, stuck in a country where the max bandwidth is 25% of the promised ones from the ISP </rant>), you can have a local installation of WP on your computer. It’s quite easy, and there are several good tutorials out there (I’d recommend this). I tinker around with my databases locally to, so that I don’t royally screw up the online version heh :)
Hi all and thanks for all the good ideas & trouble shooting.
Thanks also for trying to find the problem on my site but it wasn’t my site I was describing. If it had been my site I would have said “oh well there goes my 6 readers”.
In all seriousness, I’ve dealt with many hosts and set up many sites functioning well on various shared hosting plans, all of which were upgraded to 2.7.1 without a problem.
As for the website I wrote about in my earlier comments,there have been a few troubles since upgrading to 2.7.1 but nothing like this last doozy.
We can’t afford all the downtime. It’s definitely time to step it up to the next level and spring for more hosting power. There have been several red flags during the year and from the beginning I said the hosting wasn’t up to the demands made on it by the client’s customers. Last night’s drama sealed the deal.
Jeff
Very interesting vulnerability in WordPress. Which shows that even installers with protection from overinstall is dangerous, because this protection can be bypassed in some cases, so it’s always better to delete installations files after the install.
As I wrote in my article Attack on Abuse of Functionality in WordPress, I have created some variants of the attack on this vulnerability in WP.
It’s possible to attack site even if there is database of engine and there is connection to MySQL, but at that there is crash in one of WP’s tables (which is checking by installer). Particularly, the attack is possible when table wp_options (in WordPress 2.6.2) is damaged, or wp_users (in WordPress 2.0.3 and 2.0.11). I.e. in different versions of WP different tables is checked by installer – it can be table wp_options or wp_users (hardly possible that some other table will be checked by installer in other versions of WP).
Variants of the attack at the site on WordPress (which has installer at the site):
1. In case, if such crash in MySQL was happened on the site and such dialog of installer is showed, then it’s possible to attack the site. Taking into account that it’s very unlikely, and it’s also needed to detect the time of the crash, so better to use other variant of the attack.
2. Make DoS attack on MySQL for the attack on WordPress. Due to DoS attack there will be crash with connection to MySQL and installer potentially can show installation dialog. Though in most cases the connection to DBMS will be lost completely and installer will show corresponding message.
3. Due to automated attack on MySQL (via Insufficient Anti-automation vulnerability in WP) it’s possible to lead to crash in one of checked tables (which also is DoS attack). And in this case installer will work.
Particularly for WordPress 2.0.x and other versions of engine, where installer checked wp_users, this can be done via automated users registration. If to resister user actively at the site, then there can be crash in table wp_users and so engine will can’t read it and show dialog of installer.
@MustLive: Thank you for digging into this issue and providing everyone with a much more in-depth look into the vulnerability and how it operates. I am going to update the post with a link to this comment and your article (even though it is written in Russian ? language). Incredible work, my friend — thanks again for looking into this and advancing our knowledge of the issue. Cheers, Jeff
Many thanks to MustLive for his work on this security issue!
Your Fix#3, according to me, can cause a mail bomb.
Consider if the attacker actually reads the source of that file and plain refreshes the page, can cause those many mails to be sent.
Also, if he wants to play the vandal, he could just add it to his mailbomber and click bombs away!
@Metahuman: I don’t think so.. PHP source is not available to the browser. An attacker would have no way of knowing if their email bomb attempt was working. However, as I have painfully discovered, normal traffic can be just as much of a “mail bomb” when the server goes down. In the course of an hour, my email account receives hundreds of emails telling me that “there is a problem with teh database!” I need a script that will send one email per occurrence, so if you have any ideas, let me know!
Umm. Maybe, set up a cookie?
Maybe get it to write the value to the database and look in the database to see if its already emailed you – oh hang on……
The cookie idea sounds sensible – a value like have-emailed to true with a cookie that expires in 6 hours or something similar
Then,
if (!isset($cookievalue))
{
send the email etc.
}
would work nicely
A cookie won’t help, it would only work per user assuming they store cookies, which any kind of attacker probably won’t.
The only option for persistent storage if the database is down is the filesystem. Most WP installs can write files to wp-content/uploads so the script could create a file somewhere there with a timestamp of when the last db warning email was sent. Then one email could be generated per X hour period.