WordPress Sites Compromised via Zero-Day Vulnerabilities in Total Donations Plugin

The Wordfence Threat Intelligence team recently identified multiple critical vulnerabilities in the commercial Total Donations plugin for WordPress. These vulnerabilities, present in all known versions of the plugin up to and including 2.0.5, are being exploited by malicious actors to gain administrative access to affected WordPress sites. We have reserved CVE-2019-6703 to track and reference these vulnerabilities collectively.

It is our recommendation that site owners using Total Donations delete–not just deactivate–the vulnerable plugin as soon as possible to secure their sites. The following article details the issues present in Total Donations, as well as the active attacks against the plugin. We’ll also take a look at our disclosure process, and the steps we took in our attempts to contact the plugin’s developers to reach a resolution.

Curious Access Logs

As is the case with many investigations, this discovery was directly aided by an attacker’s mistake. During the course of remediating a compromised site, an analyst identified a series of suspicious AJAX actions in the site’s access log (extra data has been removed for clarity):

POST /wp-admin/admin-ajax.php?action=migla_getme
POST /wp-admin/admin-ajax.php?action=migla_getme
POST /wp-admin/admin-ajax.php?action=miglaA_update_me
POST /wp-admin/admin-ajax.php?action=miglaA_update_me
GET /wp-login.php?action=register

Searching the site’s codebase for the strings migla_getme and miglaA_update_me revealed the installed Total Donations plugin, and we quickly identified the exploited vulnerabilities as well as the attacker’s workflow.

The mistake made by the attacker was the use of ?action= in the query strings of these requests. WordPress’s AJAX API treats $_GET['action'] and $_POST['action'] the same, so if action was passed in the POST body of the request instead of the query string, the activity wouldn’t have been nearly as obvious.

Multiple Vulnerable AJAX Actions

Total Donations registers a total of 88 unique AJAX actions into WordPress, each of which can be accessed by unauthenticated users by querying the typical /wp-admin/admin-ajax.php endpoint. We have determined that 49 of these 88 actions can be exploited by a malicious actor to access sensitive data, make unauthorized changes to a site’s content and configuration, or take over a vulnerable site entirely.

Some of the more noteworthy issues are as follows:

Arbitrary Options Updates Leading To Site Takeover

The most immediately obvious flaws in Total Donations allow unauthenticated users to read and update arbitrary WordPress options, and these are where we’ve identified active exploitation in the wild.

add_action("wp_ajax_migla_getme", "migla_getme");
add_action("wp_ajax_nopriv_migla_getme", "migla_getme");

function migla_getme(){
$r = get_option($_POST['key']);
echo $r;
die();
}

The functions migla_getme (shown above) and migla_getme_array both allow an unprivileged attacker to read the value of any WordPress option.

add_action("wp_ajax_nopriv_miglaA_update_me", "miglaA_update_me");
add_action("wp_ajax_miglaA_update_me", "miglaA_update_me");

function miglaA_update_me() {
$key = $_POST['key'];
$value = $_POST['value'];

update_option( $key , $value);

die();
}

miglaA_update_me (shown above), miglaA_update_arr, and miglaA_update_barinfo (which is actually identical to miglaA_update_me) can all be used to modify the values of these options.

Together, migla_getme and miglaA_update_me can be used to enable the users_can_register option, and set default_role to “administrator”. With these settings in place, anyone can freely register an account with administrative permissions on the vulnerable site, where they can perform further malicious activity without issue.

Access, Modify, and Delete Recurring Stripe Payment Plans

Total Donations can connect to Stripe as a payment processor, and can make use of Stripe’s Plans API to schedule recurring donations. Unfortunately, none of the AJAX functions used to interact with these plans feature any access control.


add_action("wp_ajax_miglaA_stripe_addPlan", "miglaA_stripe_addPlan");
add_action("wp_ajax_nopriv_miglaA_stripe_addPlan", "miglaA_stripe_addPlan");

function miglaA_stripe_addPlan(){

require_once 'migla-call-stripe.php'; 
Migla_Stripe::setApiKey( migla_getSK() );

if( !isset($_POST['interval_count']) || empty($_POST['interval_count']) ){ 
$_count = 1; 
}else{ 
$_count = $_POST['interval_count'] ;
}

$plan = MStripe_Plan::create(
array(
"amount" => $_POST['amount'],
"interval" => $_POST['interval'],
"interval_count" => $_count,
"name" => $_POST['name'],
"currency" => get_option('migla_default_currency'),
"id" => $_POST['id']
)
);

$plan_array = $plan->__toArray(true);

$post_id = migla_get_stripeplan_id();

add_post_meta( $post_id, 'stripeplan_'.$_POST['id'], $plan_array );

echo $arr;
die();
}

These functions, including miglaA_stripe_addPlan (shown above), miglaA_syncPlan, and miglaA_stripe_deletePlan, can be exploited to make unauthorized changes to an affected site’s recurring donations.

Additionally, when combined with the arbitrary options update flaw present in miglaA_update_me, an attacker can modify the options which store Stripe’s API keys in order to route incoming donations to a different Stripe account entirely.

Access Constant Contact and Mailchimp Mailing Lists

Sites seeking to perform donation drives will often make use of mailing lists to drive these campaigns, and Total Donations includes functionality which can integrate its own campaigns with mailing lists through both Constant Contact and Mailchimp.

add_action("wp_ajax_miglaA_retrieve_cc_lists", "miglaA_retrieve_cc_lists");
add_action("wp_ajax_nopriv_miglaA_retrieve_cc_lists", "miglaA_retrieve_cc_lists");

function miglaA_retrieve_cc_lists()
{
$cc = new migla_constant_contact_class();
$theList = $cc->get_milist();

echo $theList;
die();
}

Just like the rest of these functions, miglaA_mailchimp_getlists and miglaA_retrieve_cc_lists (shown above) both fail to perform any sort of permissions checks before returning data associated with a connected account’s mailing lists.

Other Miscellaneous Vulnerabilities

A number of other vulnerabilities exist in the plugin, which are moot when an attacker can gain administrative access and perform any other activity manually from there. However, in the interest of illustrating the scope of Total Donation’s issues, here’s a few of them.


add_action("wp_ajax_miglaA_export_report", "miglaA_export_report");
add_action("wp_ajax_nopriv_miglaA_export_report", "miglaA_export_report");

function miglaA_export_report() 
{
global $wpdb;
$meta = array();
$meta = $wpdb->get_results(
"SELECT DISTINCT meta_key FROM {$wpdb->prefix}postmeta where meta_key like 'miglad%' OR meta_key like 'miglac%'"
);

$PID = array();
$PID = $wpdb->get_results( 
$wpdb->prepare( 
"SELECT ID FROM {$wpdb->prefix}posts WHERE post_type like %s ORDER BY ID", $_POST['post_type']
)
); 

$data = array();
$row = 0;

foreach( $PID as $id )
{
foreach( $meta as $m )
{
$data[$row][$m->meta_key] = get_post_meta( intval( $id->ID ) , $m->meta_key, true );
}
$data[$row]['id'] = intval( $id->ID ) ;
$row++;
}

echo json_encode($data);
die();
}

miglaA_export_report, a function intended to produce donation reports, can allow unauthenticated access to private and unpublished posts.

function updateACampaignCretor($old, $new , $form_id)
{
$sql = "UPDATE {$wpdb->prefix}posts SET post_title = '".$new."' WHERE ID ='".$form_id."'";
$wpdb->query($sql); 
}

miglaA_save_campaign and miglaA_save_campaign_creator both call the internal function updateACampaignCretor (sic), which can be vulnerable to SQL injection if WordPress’s wp_magic_quotes feature is bypassed. Without bypassing, it can still be used to change arbitrary post titles on a vulnerable site.


add_action("wp_ajax_miglaA_test_email", "miglaA_test_email");
add_action("wp_ajax_nopriv_miglaA_test_email", "miglaA_test_email");

function miglaA_test_email()
{
$postData = array();
$postData['miglad_email'] = $_POST['testemail'];
$postData['miglad_firstname'] = 'John';
$postData['miglad_lastname'] = 'Doe';
$postData['miglad_amount'] = 100;
$postData['miglad_date'] = date("Y-m-d", time());
$postData['miglad_address'] = "Houwei Ave Road";
$postData['miglad_country'] = "Canada";
$postData['miglad_province'] = "British Columbia";
$postData['miglad_postalcode'] = "1234";
$postData['miglad_campaign'] = 'Save Sun Bears';
$postData['miglad_repeating'] = 'no';
$postData['miglad_anonymous'] = 'no';
$ne = get_option('migla_notif_emails');

$test = mg_send_thank_you_email( $postData, $_POST['email'], $_POST['emailname'] );
mg_send_notification_emails( $postData, $_POST['email'], $_POST['emailname'] , $ne );

if( $test ){ 
echo "Email has been sent to ".$_POST['testemail']; 
} else { 
echo "Sending email failed"; 
}

die();
}

Multiple actions, including miglaA_test_email, can be abused by an attacker to send test emails to an arbitrary address. This can be automated as a Denial of Service (DoS) for outbound email, either by triggering a host’s outgoing mail relay limits, or by causing the victim site to appear on spam blacklists.

Plugin Abandoned, No Developer Response

These security flaws are considered zero-day vulnerabilities due to their active exploitation and a lack of an available patch. On January 16th, we worked to contact Total Donations’ development team, Calmar Webmedia, in order to work together to produce a patch and protect affected users. Unfortunately, the process of making this contact revealed that a solution may not ever be coming.

The official Total Donations site currently displays this Coming Soon splash page, and has since May 2018.

There currently do not appear to be any legitimate means of acquiring the latest version of Total Donations. The plugin’s homepage currently displays a Coming Soon page, featuring a mockup image of a new website. The upload path of this image implies the site has been in this state since May 2018.

The plugin was formerly distributed via Envato’s CodeCanyon marketplace. Total Donations is no longer available for purchase, but its reviews page is still accessible.

Negative reviews about absent developer support go back years.

The most common issue cited in these reviews is a lack of product support, with complaints up to three years old detailing a complete lack of responsiveness from the plugin’s developers. As a security researcher hoping to make urgent contact regarding an active threat, this was an early bad omen.

The seller profile associated with Total Donations contained information and support links to Calmar Webmedia, a Vancouver-based development firm. However, this project appears to have been abandoned as well. The company’s support page loads a blank screen, likely a White Screen of Death, and the Request A Quote page just displays a nonfunctional shortcode.

One still-functional element of Calmar Webmedia’s site is a JavaScript animation of a stick figure running from a tank, present in the footer of every page.

Ultimately, the only official method of contacting Calmar Webmedia was a contact form available in the footer of the company’s home page. On January 16th we submitted a request for contact through this form, and have not received any acknowledgement from Calmar Webmedia.

Because of the lack of any developer response, the apparent abandonment of the Total Donations plugin, and the active attacks on vulnerable sites, as per our published disclosure policy we have released this disclosure as a means of making the community aware of the threat.

Delete the Plugin, Don’t Just Deactivate It

As we suggested earlier in this post, in order to completely secure an affected site the Total Donations plugin should be deleted entirely, not just deactivated. This is due to an alternate AJAX endpoint built into Total Donations by its developers.


$action = esc_attr(trim($_POST['action']));

//For logged in users
add_action("wp_ajax_".$action , $action); 
add_action("wp_ajax_nopriv_".$action , $action);

if(is_user_logged_in())
do_action('wp_ajax_'.$action);
else
do_action('wp_ajax_nopriv_'.$action);

The script, the-ajax-caller.php, loads the site’s WordPress environment and subsequently registers and executes whichever AJAX action is passed, even when Total Donations is deactivated. This can also be used to call any arbitrary function, regardless of whether it’s associated with the Total Donations plugin at all, posing additional security risks on its own.

Next Steps

We will continue to track malicious activity associated with CVE-2019-6703 going forward, and will report on any noteworthy changes in this activity.

We have released a firewall rule to protect sites using the Wordfence WAF from these vulnerabilities, which our Premium subscribers have already received. Sites protected by the free version will receive it following our standard delay of thirty days. Given the severity of the vulnerabilities we strongly recommend that everyone, including our Premium customers, remove the plugin from their site and seek an alternative.

Please consider sharing this disclosure in order to improve awareness of these threats, and if someone you know uses the plugin on one of their sites warn them immediately.

For any questions regarding our decision to disclose these unpatched flaws, please refer to our Vulnerability Disclosure Policy.

Credits: Initial attack data discovered by Security Analyst Nate Smith. Vulnerability assessment and vendor research performed by Threat Analyst Mikey Veenstra, with additional review by Matt Rusnak and Ramuel Gall. Edited by Dan Moen and Mark Maunder.

Did you enjoy this post? Share it!

Comments

6 Comments
  • Thank you for sharing these discovers.

    I have a website using a theme (Helpme by DesignVilla [please moderate if you think these names should not be published]) that has a customized version of this plugin bundled as its "required" ones.
    I never installed it (via Appearance > Install plugins) because didn't liked the way it works. Anyway I checked the plugins folder and luckily there's no totaldonations folder there.

    By taking a look at the parent theme files I found that the plugin gets downloaded from the theme producer's server if one choose to install it.
    Please note: this is perfectly normal and legitimate with commercial themes that contains bundled plugins, I just wanted to share this "alternative" way to install/uninstall it and ask if you can reassure us that if a folder is not present in the plugins directory that means it is no installed.

    Thank you again for your precious work.

  • Wow. Thank you for the walkthrough.
    As a new programmer, this is an invaluable lesson on why security is so important and specifically where one might fail to properly implement it.
    I learned a lot :D

  • Thanks! Good to know that.
    It's great, to know you have our backs... that is, in a game with this many variables, worth a lot!!!

    Great Job! Keep it up!

  • Wow. So detailed and fascinating! Thanks for such kind of information. The recommended links are awesome too and got the several meaningful information from there.

  • Is there anything that can be done at the SQL or Database level to clean up anything? Or is deleting the plugin folder sufficient enough? Thank you for your investigation and documentation btw.

    • Hi Evan! Deleting the plugin directory from your site is all you need to secure the vulnerability, though Total Donations does create database entries which can safely be removed for maintenance reasons.