Headline
CVE-2019-20360: Authentication Bypass Vulnerability in GiveWP Plugin
A flaw in Give before 2.5.5, a WordPress plugin, allowed unauthenticated users to bypass API authentication methods and access personally identifiable user information (PII) including names, addresses, IP addresses, and email addresses. Once an API key has been set to any meta key value from the wp_usermeta table, and the token is set to the corresponding MD5 hash of the meta key selected, one can make a request to the restricted endpoints, and thus access sensitive donor data.
Description: Authentication Bypass with Information Disclosure
CVSS v3.0 Score: 7.5 (High)
CVSS Vector String: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Affected Plugin: GiveWP
Plugin Slug: give
Affected Versions: <= 2.5.4
Patched Version: 2.5.5
A few weeks ago, our Threat Intelligence team discovered a vulnerability present in GiveWP, a WordPress plugin installed on over 70,000 websites. The weakness allowed unauthenticated users to bypass API authentication methods and potentially access personally identifiable user information (PII) like names, addresses, IP addresses, and email addresses which should not be publicly accessible.
We privately disclosed the issue to the plugin’s developer on September 3rd, who were quick to respond and released a patch shortly after. Wordfence Premium customers received a new firewall rule on September 4th to protect against exploits targeting this vulnerability. Free Wordfence users will receive the rule after thirty days.
This is considered a high security issue, and websites running Give 2.5.4 or below should be updated to version 2.5.5 or later right away.
Vulnerability In Detail
GiveWP provides users with an API functionality in order to integrate donation data into webpages and applications like Zapier. Site owners are able to generate a unique API key along with a token and private key that can be used to access restricted endpoints and gain access to donation data. However, it turned out that if no API key was generated, any user was able to access restricted endpoints by simply selecting any meta key from the wp_usermeta table and setting that as the authentication key. For instance, the nickname or session_tokens meta key (which are defined for all users) could have been supplied instead of a valid API key. There is also an authentication token that is used to validate this API request, however, for users that have not generated an API key, the authentication token is simply just the MD5 hash of the meta key that is used in place of a valid API key.
The API key validation method can be found in the validate_request() method seen below.
private function validate_request() { global $wp_query;
$this->override = false;
// Make sure we have both user and api key
if ( ! empty( $wp\_query->query\_vars\['give-api'\] ) && ( $wp\_query->query\_vars\['give-api'\] !== 'forms' || ! empty( $wp\_query->query\_vars\['token'\] ) ) ) {
if ( empty( $wp\_query->query\_vars\['token'\] ) || empty( $wp\_query->query\_vars\['key'\] ) ) {
$this->missing\_auth();
return false;
}
// Retrieve the user by public API key and ensure they exist
if ( ! ( $user = $this->get\_user( $wp\_query->query\_vars\['key'\] ) ) ) {
$this->invalid\_key();
return false;
} else {
$token = urldecode( $wp\_query->query\_vars\['token'\] );
$secret = $this->get\_user\_secret\_key( $user );
$public = urldecode( $wp\_query->query\_vars\['key'\] );
if ( hash\_equals( md5( $secret . $public ), $token ) ) {
$this->is\_valid\_request = true;
} else {
$this->invalid\_auth();
return false;
}
}
} elseif ( ! empty( $wp\_query->query\_vars\['give-api'\] ) && $wp\_query->query\_vars\['give-api'\] === 'forms' ) {
$this->is\_valid\_request = true;
$wp\_query->set( 'key', 'public' );
}
}
The first check verifies if there is a valid user and API key, if there is no token or API key set then the request is automatically denied from moving further. If we have both the token and key set, we can then move on to retrieving the user by the public API key that was set and this is where we find our first major problem.
We then get to the get_user() method that does a check based on the API key that was provided. Unfortunately, in this check it doesn’t verify if the key was one generated by the Give API, but rather just fetches the user_id for any meta key in the wp_usermeta table, so if we pass in nickname or session_tokens as our meta key we’ll get back a valid user.
public function get\_user( $key = '' ) {
global $wpdb, $wp\_query;
if ( empty( $key ) ) {
$key = urldecode( $wp\_query->query\_vars\['key'\] );
}
if ( empty( $key ) ) {
return false;
}
$user = Give\_Cache::get( md5( 'give\_api\_user\_' . $key ), true );
if ( false === $user ) {
$user = $wpdb->get\_var( $wpdb->prepare( "SELECT user\_id FROM $wpdb->usermeta WHERE meta\_key = %s LIMIT 1", $key ) );
Give\_Cache::set( md5( 'give\_api\_user\_' . $key ), $user, DAY\_IN\_SECONDS, true );
}
if ( $user != null ) {
$this->user\_id = $user;
return $user;
}
return false;
}
The next check is to verify a “signature” of the user’s API key which is the MD5 hash of a secret key concatenated with the supplied API key. The secret key is normally defined when an API key is created for a given user. For user’s that have not generated an API key, the secret is an empty value, so a valid signature can be forged using just the MD5 value of the meta key that we’ve passed in instead of a valid API key.
$token = urldecode( $wp\_query->query\_vars\['token'\] );
$secret = $this->get\_user\_secret\_key( $user );
$public = urldecode( $wp\_query->query\_vars\['key'\] );
if ( hash\_equals( md5( $secret . $public ), $token ) ) {
$this->is\_valid\_request = true;
Taking a Look at the Exploit
Once the key has been set to any meta key value from the wp_usermeta table, and the token is set to the corresponding MD5 hash of the meta key selected, you can make a request to the restricted endpoints accessing sensitive donor data. In this example, we used the meta_key session_tokens and the MD5 hash of the string session_tokens which is ea78b7d35ff75719b36056cfa14ddcc8.
Personally Identifiable Information displayed when accessing vulnerable “donations” endpoint from GiveWP plugin.
As you can see, information in these endpoints can contain PII like names, addresses, IP addresses, and email addresses which should not be publicly accessible.
Disclosure Timeline
September 3rd – Plugin developer notified of the security issue
September 4th – Firewall rule released to Wordfence Premium users
September 20th – Patch released
October 4th – Firewall rule becomes available to free users
Conclusion
In today’s post, we detailed an authentication bypass flaw present in the GiveWP plugin. This flaw has been patched in version 2.5.5 and we recommend users update to the latest version available. Sites running Wordfence Premium have been protected from attacks against this vulnerability since September 4th, 2019. Sites running the free version of Wordfence will receive the firewall rule update on October 4th, 2019.
Thank you to the plugin’s team at GiveWP, for their extremely prompt response and cooperation to get a fix out quickly, and to Matt Barry, Wordfence’s Lead Developer, for his assistance in researching this vulnerability.