PublishPress Revisions: Duplicate Posts, Submit, Approve and Schedule Content Changes <= 3.7.22 - Cross-Site Request Forgery
Description
The PublishPress Revisions: Duplicate Posts, Submit, Approve and Schedule Content Changes plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 3.7.22. This is due to missing or incorrect nonce validation on a function. This makes it possible for unauthenticated attackers to perform an unauthorized action granted they can trick a site administrator 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:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.7.23
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-25322 (PublishPress Revisions CSRF) ## 1. Vulnerability Summary The **PublishPress Revisions** plugin (up to 3.7.22) is vulnerable to **Cross-Site Request Forgery (CSRF)** due to missing nonce validation in the `rvy_admin_init()` function located in `admin/ad…
Show full research plan
Vulnerability Research Plan: CVE-2026-25322 (PublishPress Revisions CSRF)
1. Vulnerability Summary
The PublishPress Revisions plugin (up to 3.7.22) is vulnerable to Cross-Site Request Forgery (CSRF) due to missing nonce validation in the rvy_admin_init() function located in admin/admin-init_rvy.php. This function handles the updating of plugin settings. Specifically, when the rvy_submit POST parameter is present, the plugin proceeds to save configuration options without verifying a security nonce. This allows an unauthenticated attacker to change site-wide revision settings by tricking an administrator into submitting a forged request.
2. Attack Vector Analysis
- Vulnerable Endpoint: Any administrative URL (e.g.,
/wp-admin/admin.php?page=revisionary-settings), as the vulnerable function is hooked toadmin_init. - Action: Plugin settings update.
- Vulnerable Parameter:
rvy_submit. - Required Authentication: Administrator (the victim must have an active session).
- Preconditions: The attacker must trick the administrator into clicking a link or visiting a malicious page that triggers a POST request to the WordPress admin panel.
3. Code Flow
- Hook Registration: The plugin likely registers
rvy_admin_initon theadmin_inithook (standard WordPress behavior for functions named*_admin_init). - Entry Point:
admin/admin-init_rvy.php:49if ( ! empty($_POST['rvy_submit']) || ! empty($_POST['rvy_defaults']) ) { require_once( RVY_ABSPATH . '/submittee_rvy.php'); $handler = new Revisionary_Submittee(); if ( isset($_POST['rvy_submit']) ) { $sitewide = isset($_POST['rvy_options_doing_sitewide']); $customize_defaults = isset($_POST['rvy_options_customize_defaults']); $handler->handle_submission( 'update', $sitewide, $customize_defaults ); } // ... } - Missing Validation: Notice that between line 49 and line 57, there is no call to
check_admin_referer()orwp_verify_nonce(). - Execution: The
Revisionary_Submittee::handle_submissionmethod (insubmittee_rvy.php) iterates through the$_POSTdata and updates the plugin's option (usually stored in therevisionary_optionsarray in the database).
4. Nonce Acquisition Strategy
No nonce is required for this exploit.
The vulnerability exists specifically because the plugin fails to check for a nonce before processing the rvy_submit action. An attacker can submit the request with zero knowledge of site secrets or nonces.
5. Exploitation Strategy
The goal is to change a visible setting, such as the revision_limit_per_post, to prove unauthorized modification of plugin configuration.
HTTP Request (PoC)
The security agent will perform an authenticated POST request as an Administrator (simulating the victim's browser being hijacked via CSRF).
- URL:
http://localhost:8080/wp-admin/admin.php?page=revisionary-settings - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
rvy_submit=1&revision_limit_per_post=1337&pending_revisions=1 - Tool:
http_request
6. Test Data Setup
- Install Plugin: Ensure PublishPress Revisions <= 3.7.22 is active.
- Check Current State:
(Identify ifwp option get revisionary_optionsrevision_limit_per_postis currently set to something other than1337).
7. Expected Results
- The server should return a
302 Redirect(standard WordPress admin behavior after saving settings) or a200 OKif the redirect is followed. - The plugin will process the
handle_submissioncall. - The
revision_limit_per_postvalue in therevisionary_optionsarray will be updated to1337.
8. Verification Steps
After the http_request is sent, verify the modification using WP-CLI:
# Get the options and check the specific key
wp option get revisionary_options --format=json | grep -o '"revision_limit_per_post":"1337"'
If the grep returns a match, the CSRF was successful.
9. Alternative Approaches
If revision_limit_per_post does not update (e.g., if the plugin expects specific input formats), try targeting the pending_revisions checkbox or the copy_posts_capability setting:
- Payload:
rvy_submit=1&pending_revisions=0(to disable pending revisions). - Payload:
rvy_submit=1&revision_queue_capability=subscriber(to potentially allow lower-privileged users to see the revision queue, if the capability is not strictly validated during save).
Note: checkboxes in WordPress settings often require the value '1' to be considered "checked" and are absent from POST if unchecked. The exploit should send rvy_submit=1 plus the target option name and its desired value.
Summary
The PublishPress Revisions plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) due to missing nonce validation in the settings processing logic and several AJAX actions. This allows unauthenticated attackers to modify plugin settings or perform unauthorized revision management tasks by tricking an administrator into interacting with a malicious link or page.
Vulnerable Code
// admin/admin-init_rvy.php:66 if ( ! empty($_POST['rvy_submit']) || ! empty($_POST['rvy_defaults']) ) { require_once( RVY_ABSPATH . '/submittee_rvy.php'); $handler = new Revisionary_Submittee(); if ( isset($_POST['rvy_submit']) ) { $sitewide = isset($_POST['rvy_options_doing_sitewide']); $customize_defaults = isset($_POST['rvy_options_customize_defaults']); $handler->handle_submission( 'update', $sitewide, $customize_defaults ); } // ... } --- // admin/post-edit_rvy.php:348 (Example of AJAX CSRF vector without nonce check) $(document).on('change', 'div.rvy-author-selection select', function(e) { var data = {'rvy_ajax_field': 'author_select', 'rvy_ajax_value': <?php echo esc_attr($post->ID);?>, 'rvy_selection': $('div.rvy-author-selection select').val(), 'nc': Math.floor(Math.random() * 99999999)};
Security Fix
@@ -66,6 +66,8 @@ if ( ! empty($_POST['rvy_submit']) || ! empty($_POST['rvy_defaults']) ) { + check_admin_referer('rvy-update-options'); require_once( RVY_ABSPATH . '/submittee_rvy.php'); $handler = new Revisionary_Submittee(); @@ -113,6 +113,11 @@ wp_enqueue_script( 'rvy_object_edit', RVY_URLPATH . "/admin/rvy_post-block-edit{$suffix}.js", array('jquery', 'jquery-form'), PUBLISHPRESS_REVISIONS_VERSION, true ); $args = \PublishPress\Revisions\PostEditorWorkflowUI::postLinkParams(compact('post', 'do_pending_revisions', 'do_scheduled_revisions')); + + $args['createRevisionNonce'] = wp_create_nonce('create_revision'); + $args['submitRevisionNonce'] = wp_create_nonce('submit_revision'); + $args['createScheduledRevisionNonce'] = wp_create_nonce('create_scheduled_revision'); + $args['authorSelectNonce'] = wp_create_nonce('author_select'); } $wp_timezone = wp_timezone(); @@ -181,7 +186,7 @@ }); $(document).on('change', 'div.rvy-author-selection select', function(e) { - var data = {'rvy_ajax_field': 'author_select', 'rvy_ajax_value': <?php echo esc_attr($post->ID);?>, 'rvy_selection': $('div.rvy-author-selection select').val(), 'nc': Math.floor(Math.random() * 99999999)}; + var data = {'rvy_ajax_field': 'author_select', 'rvy_ajax_value': <?php echo esc_attr($post->ID);?>, 'rvy_selection': $('div.rvy-author-selection select').val(), '_rvynonce': '<?php echo esc_attr(wp_create_nonce('author_select'));?>'}; $('div.rvy-author-selection select').attr('disabled', 'disabled');
Exploit Outline
The exploit targets the plugin's settings update mechanism and various AJAX-based management actions that lack nonce verification. An attacker can craft a POST request to any administrative URL (since the vulnerable function is hooked to 'admin_init'). 1. Methodology: For settings modification, the attacker creates a hidden HTML form that submits to `/wp-admin/admin.php?page=revisionary-settings`. 2. Payload Shape: The POST body must contain `rvy_submit=1` along with the target option keys and values (e.g., `revision_limit_per_post=1337`). 3. AJAX Vector: For management actions (like revision creation or author changes), the attacker triggers a request with `rvy_ajax_field=create_revision` and target parameters without providing a `_rvynonce` token. 4. Requirements: The victim must be a logged-in site administrator who visits a malicious page or clicks a link controlled by the attacker while their session is active.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.