$4,998 Bounty Awarded and 100,000 WordPress Sites Protected Against Unauthenticated Remote Code Execution Vulnerability Patched in GiveWP WordPress Plugin
📢 Did you know Wordfence runs a Bug Bounty Program for all WordPress plugin and themes at no cost to vendors? Through October 14th, researchers can earn up to $31,200, for all in-scope vulnerabilities submitted to our Bug Bounty Program! Find a vulnerability, submit the details directly to us, and we handle all the rest.Â
On May 26th, 2024, we received a submission for an unauthenticated PHP Object Injection to Remote Code Execution vulnerability in GiveWP, a WordPress plugin with more than 100,000 active installations. This vulnerability can be leveraged via an existing POP chain present in the plugin to execute code remotely and delete arbitrary files.
Props to villu164 who discovered and responsibly reported this vulnerability through the Wordfence Bug Bounty Program. This researcher earned a bounty of $4,998.00 for this discovery. Our mission is to Secure the Web, which is why we are investing in quality vulnerability research and collaborating with researchers of this caliber through our Bug Bounty Program. We are committed to making the WordPress ecosystem more secure, which ultimately makes the entire web more secure.
All Wordfence Premium, Wordfence Care, and Wordfence Response customers, as well as those using the free version of our plugin, are protected against any exploits targeting this vulnerability by the Wordfence firewall’s built-in PHP Object Injection protection.
We contacted the StellarWP team on June 13, 2024. After not receiving a reply, we escalated the issue to the WordPress.org Security Team on July 6, 2024. After that, the developer team released a patch on August 7, 2024.
We urge users to update their sites with the latest patched version of GiveWP, version 3.14.2 at the time of this writing, as soon as possible.
Vulnerability Summary from Wordfence Intelligence
Affected Plugin: GiveWP – Donation Plugin and Fundraising Platform
Plugin Slug: give
Affected Versions: <= 3.14.1
CVE ID: CVE-2024-5932
CVSS Score: 10.0 (Critical)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Researcher/s: villu164
Fully Patched Version: 3.14.2
Bounty Award: $4,998.00
The GiveWP – Donation Plugin and Fundraising Platform plugin for WordPress is vulnerable to PHP Object Injection in all versions up to, and including, 3.14.1 via deserialization of untrusted input from the ‘give_title’ parameter. This makes it possible for unauthenticated attackers to inject a PHP Object. The additional presence of a POP chain allows attackers to execute code remotely, and to delete arbitrary files.
How Does PHP Object Injection Work?
Before we dive into the technical details of this vulnerability, we’ll provide some background on what exactly PHP Object Injection is and how it works. This has been covered in more depth in a previous article we wrote detailing another PHP Object Injection vulnerability.
PHP uses serialization to store complex data. Serialized data often looks like this:
a:2:{s:11:"productName";s:5:"apple";s:7:"price";i:10;}
In this payload, productName
is set to “apple” and price
is set to 10.
Serialized data is useful for storing settings in bulk, and WordPress uses it for many of its settings. However, it can also cause a security issue because it can be used to store PHP objects.
What Are PHP Objects?
Most modern PHP code is object oriented, where code is organized into “classes”. Classes serve as templates with variables (called “properties”) and functions (called “methods”). Programs create “objects” from these classes, resulting in more reusable, maintainable, scalable, and robust code.
For example, an online store might use a single class for products with properties like $price
and $productName
, creating different objects for each product. Each object could use the same method to calculate tax, but with different prices and names.
If a plugin unserializes user-provided data without sanitizing it, an attacker could inject a payload that becomes a PHP object when unserialized. While a simple PHP object isn’t dangerous, the situation changes if the class includes magic methods.
What Are Magic Methods?
Magic methods are special functions in a class that define behavior during certain events. For example, the __destruct
method is used to clean up when an object is no longer needed.
Here’s a simple example of a vulnerable class that calculates product prices, stores a log, and deletes the log when there are no remaining references to the object:
class Product { public $price; public $productName; public $savedPriceFile; function __construct( $price, $productName ) { $this->price = $price; $this->productName = $productName; $this->savedPriceFile = $productName . "pricefile.log"; } function calculateTotal( $quantity ) { $total = $this->price * $quantity; echo $total; file_put_contents( $this->savedPriceFile, $total ); } function __destruct() { unlink( $this->savedPriceFile ); } }
If this code is executed on a site with a PHP Object Injection vulnerability, an attacker could exploit it to delete arbitrary files. For example, this payload deletes the wp-config.php
file:
O:7:"Product":3:{s:5:"price";i:2;s:11:"productName";s:6:"apples";s:14:"savedPriceFile";s:13:"wp-config.php";}
This would inject a Product object with $productName
set to “apples”, $price
set to 2, and $savedPriceFile
set to “wp-config.php”. Even though the object might not be used by anything else, eventually the __destruct
method would run and delete the file specified in $savedPriceFile
. In this case, deleting wp-config.php
would reset the site, potentially allowing an attacker to take over by connecting it to a remote database they control.
Technical Analysis
GiveWP is a WordPress donation plugin, which includes many features, such as customizable donation forms, donors management, reports management, integration with third-party gateways and services, and much more.
Examining the code reveals that the plugin uses the give_process_donation_form()
function to handle and process donations.
The function validates the post data right at the beginning using the give_donation_form_validate_fields()
function. This validation function checks, among other things, whether the post data contains serialized values using the give_donation_form_has_serialized_fields()
function.
if (give_donation_form_has_serialized_fields($post_data)) { give_set_error('invalid_serialized_fields', esc_html__('Serialized fields detected. Go away!', 'give')); }
Unfortunately, the function does not include the “give_title” post parameter in the vulnerable version.
Once the validation is complete, the give_process_donation_form()
function calls the give_get_donation_form_user()
function, which contains the following code snippet:
if ( empty( $user['user_title'] ) || strlen( trim( $user['user_title'] ) ) < 1 ) { $user['user_title'] = ! empty( $post_data['give_title'] ) ? strip_tags( trim( $post_data['give_title'] ) ) : ''; }
This code sets the value of the user’s “user_title” based on the “give_title” post parameter.
Then, the give_process_donation_form()
function sets the $donation_data
, which includes, among other values, the $user_info
based on the previous $user
values, and then calls the give_send_to_gateway()
function.
$donation_data = [ 'price' => $price, 'purchase_key' => $purchase_key, 'user_email' => $user['user_email'], 'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), 'user_info' => stripslashes_deep( $user_info ), 'post_data' => $post_data, 'gateway' => $valid_data['gateway'], 'card_info' => $valid_data['cc_info'], ];
give_send_to_gateway( $donation_data['gateway'], $donation_data );
This function sends all the payment data to the specified gateway. During data processing, it also adds the donor using the insert()
function in the DonorRepository
class, which contains the following code snippet:
DB::table('give_donors') ->insert($args); $donorId = DB::last_insert_id(); foreach ($this->getCoreDonorMeta($donor) as $metaKey => $metaValue) { DB::table('give_donormeta') ->insert([ 'donor_id' => $donorId, 'meta_key' => $metaKey, 'meta_value' => $metaValue, ]); }
private function getCoreDonorMeta(Donor $donor): array { return [ DonorMetaKeys::FIRST_NAME => $donor->firstName, DonorMetaKeys::LAST_NAME => $donor->lastName, DonorMetaKeys::PREFIX => $donor->prefix ?? null, ]; }
The prefix meta will be saved in the database with the key _give_donor_title_prefix
, and its value is the serialized value of the originally set user’s “user_title”.
Even in this payment processing, the _give_donor_title_prefix
meta is called with the get_meta()
function in the setup_user_info()
function in Give_Payment
class, which unserializes the previously saved serialized object:
$user_info[ $key ] = Give()->donor_meta->get_meta( $donor->id, '_give_donor_title_prefix', true );
The presence of a PHP POP chain allows an attacker to execute arbitrary code, delete arbitrary files, and potentially take over a vulnerable site.
The researcher provided us with a complex and creative exploit that includes a POP chain payload which invokes the shell_exec()
function and uploads a web shell to the server.
The POP Chain for Remote Code Execution
The complete PHP POP chain looks like this:
Please note that the source code is shortened for better readability, so only the relevant functions are included in the code snippets.
The Give\PaymentGateways\DataTransferObjects\GiveInsertPaymentData
class contains a $userInfo
array property. It uses the “address” element of this array in the getLegacyBillingAddress()
function, where it queries the billing address properties from the object.
namespace Give\PaymentGateways\DataTransferObjects; final class GiveInsertPaymentData { public $userInfo; private function getLegacyBillingAddress() { $donorDonationBillingAddress = $this->userInfo['address']; $address = [ 'line1' => $donorDonationBillingAddress->address1, 'line2' => $donorDonationBillingAddress->address2, 'city' => $donorDonationBillingAddress->city, 'state' => $donorDonationBillingAddress->state, 'zip' => $donorDonationBillingAddress->zip, 'country' => $donorDonationBillingAddress->country, ]; if (! $donorDonationBillingAddress->country) { $address = false; } return $address; } }
The $userInfo['address']
in the GiveInsertPaymentData
class instance can be set to an instance of the Give
class. In the Give
class, the $container
property can be set, and when the __get()
magic method is called, for example when the address1
property is queried, it invokes the get()
method of the container.
class Give { public function __get( $propertyName ) { return $this->container->get( $propertyName ); } }
The $container
in the Give
class instance can be set to an instance of the Give\Vendors\Faker\ValidGenerator
class. In this faker generator class, the $generator
, $validator
and $maxRetries
properties can be set, and when any method of this class is called, for example when the get()
method is called, it invokes the __call()
magic method, which then uses the call_user_func()
function to invoke the function defined in the $validator
property.
namespace Give\Vendors\Faker; class ValidGenerator { public function __call($name, $arguments) { $i = 0; do { $res = call_user_func_array([$this->generator, $name], $arguments); ++$i; if ($i > $this->maxRetries) { throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries)); } } while (!call_user_func($this->validator, $res)); return $res; } }
This allows an attacker to invoke the shell_exec()
function through call_user_func()
and execute arbitrary code on the server.
The POP Chain for Arbitrary File Deletion
The plugin loads and uses the TCPDF PHP library, which is used to generate PDF files.
class TCPDF { public function __destruct() { // cleanup $this->_destroy(true); } }
The destructor of the TCPDF
class calls the _destroy()
function, which contains the following code snippet:
foreach($this->imagekeys as $file) { if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) { @unlink($file); } }
This deletes the files in the imagekeys
property with the unlink()
function.
This allows an attacker to arbitrarily delete files from the server by creating a TCPDF
object and setting the imagekeys
property when the object is destructed.
Disclosure Timeline
May 26, 2024 – We received the submission for the PHP Object Injection to Remote Code Execution vulnerability in GiveWP via the Wordfence Bug Bounty Program.
June 10, 2024 – We validated the report and confirmed the proof-of-concept exploit.
June 13, 2024 – We sent the full disclosure details to the vendor’s known email address.
June 28, 2024 – We did not receive a response from the plugin developer and sent a follow-up contact.
July 6, 2024 – We escalated the vulnerability to the WordPress.org Security Team and sent over the full disclosure details.
August 7, 2024 – The fully patched version of the plugin, 3.14.2, is released.
Conclusion
In this blog post, we detailed a PHP Object Injection to Remote Code Execution vulnerability within the GiveWP plugin affecting versions 3.14.1 and earlier. This vulnerability allows authenticated threat actors to execute malicious code on the server. The vulnerability has been fully addressed in version 3.14.2 of the plugin.
We encourage WordPress users to verify that their sites are updated to the latest patched version of GiveWP as soon as possible considering the critical nature of this vulnerability.
All Wordfence users, including those running Wordfence Premium, Wordfence Care, and Wordfence Response, as well as sites running the free version of Wordfence, are fully protected against this vulnerability.
If you know someone who uses this plugin on their site, we recommend sharing this advisory with them to ensure their site remains secure, as this vulnerability poses a significant risk.
Comments