Improving WordPress Password Security
We’ve released the wp-password-bcrypt plugin to improve WordPress password security by using bcrypt instead of insecure MD5 password hashing.
Outdated PHP requirements
WordPress, and its community, love to parrot that it powers 25% of the web. The downside is when you’re doing something wrong, you’re affecting 1/4 of all websites.
Roots has long been critics of the out-dated PHP version requirements in WordPress. They still have 5.2 as the minimum version which has been end of life (EOL) since January 6th 2011. It is so old that it doesn’t even appear on PHP’s version calendar. It is so old that even PHP 5.5 won’t be supported in 4 months (July 10th 2016).
WordPress’ core team stance on bumping the PHP version requirement is two fold:
- Too many WP users are still on old versions like 5.2 and 5.3
- They don’t care about new “features”
This breaks down when it comes to security though. As mentioned above, PHP 5.2 has not received security fixes since 2011. Ditto for 5.3 (August 2014) and 5.4 (September 2015).
Password hashing
One of the most important features PHP has introduced was strong password hashing in 5.5. This includes password_hash
and password_verify
functions which default to the strong and more secure bcrypt.
Most developers are familiar with password hashing as it’s become common knowledge to never store passwords in plain text. Password hashing lets us store a hashed version of the password which is practically impossible to invert making it much more secure.
Just because you can’t invert a hash to find it’s original input (password in this case), doesn’t mean we’re completely secure. It’s possible for attackers to work forwards instead of backwards. They can attempt to brute-force passwords by generating hashes of all possible combinations to find a match.
Rainbow tables, and modern hardware, make calculating every combination faster than ever. As the speed of calculations has increased, the list of broken hash functions has grown.
MD5
MD5 is a broken hash function:
The security of the MD5 has been severely compromised, with its weaknesses having been exploited in the field, most infamously by the Flame malware in 2012. The CMU Software Engineering Institute considers MD5 essentially “cryptographically broken and unsuitable for further use”.
MD5 may sound familiar. It’s the hashing function that WordPress uses. In fairness to WordPress, they combine MD5 with a salt.
Unfortunately, MD5 + salting is also broken and this has been known for a long time.
MD5 is considered “broken” due to its collision vulnerability, but it’s broken more fundamentally for passwords: it’s too cheap and fast to calculate a hash.
In fact, even the WordPress core team knows it’s broken: https://core.trac.wordpress.org/ticket/21022.
That ticket was opened over 4 years ago. WordPress uses a library called phpass. It’s a portable PHP password hashing framework that’s included in WP core.
This is how WordPress creates a hasher:
$wp_hasher = new PasswordHash( 8, true );
The 2nd parameter of true
is the important one. That boolean flag tells phpass whether to use a “portable” hash. If that flag was false
then phpass would check for the existence of a strong hashing function such as bcrypt and use it.
Since WordPress is always setting that flag as true
, 25% of the web defaults to the more insecure MD5 hashing. To be fair, switching that flag wouldn’t magically make all 25% more secure. But any site at least on PHP >= 5.3.7 would, which is at least 54% of all WP sites and likely much more than that.
So what is holding up the switch? Bureaucracy and the unwillingness to make it happen.
The consensus of the ticket is that it’s actually a UX problem. At this point, there is no technical reason why this can’t be done.
The WordPress core team simply doesn’t care enough about this issue to solve a UX problem which would make every WordPress site, and its users, more secure. You would hope that a CMS/framework powering 25% of the web would take its responsibility as a leader a little more seriously and set an example.
bcrypt
I’ve talked a lot about MD5 above and why it’s now unsuitable as a password hashing function. Why is bcrypt so much better? It was designed for passwords so it’s slower and more expensive to calculate.
bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.
The “interation count”, or cost/work factor, defaults to 10 in PHP and can be configured.
wp-password-bcrypt
Roots has created a new wp-password-bcrypt plugin to address this problem. It overrides the default WordPress functions wp_hash_password
and wp_check_password
to use the built-in PHP password_hash
and password_verify
functions which use bcrypt.
There are other ways to do this but this is the simplest most direct way. The only requirement is you need PHP 5.5.
The best and easiest way to install it is through Composer:
composer require roots/wp-password-bcrypt
.
Or you can add it to your composer.json
file:
"require": {
"php": ">=8.0",
"roots/wordpress": "6.1.1",
"roots/wp-password-bcrypt": "^1.0.0"
}
You can also install it manually as an mu-plugin (wp-password-bcrypt.php
). For more installation details, see the README.
wp-password-bcrypt is also being added as a default plugin to Bedrock because we believe in more secure defaults (see the PR).