CVE-2026-25332

Endless Posts Navigation <= 2.2.9 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.3.0
Patched in
87d
Time to patch

Description

The Endless Posts Navigation plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 2.2.9. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.2.9
PublishedFebruary 7, 2026
Last updatedMay 4, 2026

What Changed in the Fix

Changes introduced in v2.3.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This analysis identifies a critical authorization bypass in the **Endless Posts Navigation** plugin (<= 2.2.9) via its REST API implementation. ### 1. Vulnerability Summary The vulnerability exists in the `QR_Code_Settings` class located in `io/functions-inner.php`. The plugin registers custom REST…

Show full research plan

This analysis identifies a critical authorization bypass in the Endless Posts Navigation plugin (<= 2.2.9) via its REST API implementation.

1. Vulnerability Summary

The vulnerability exists in the QR_Code_Settings class located in io/functions-inner.php. The plugin registers custom REST API routes for reading and updating plugin settings to support a companion mobile app. These routes explicitly set 'permission_callback' => '__return_true', allowing unauthenticated access. While the plugin attempts to implement its own authentication using a login_key parameter, it checks this key against a hardcoded value: "123" (the base64-decoded value of MTIz).

2. Attack Vector Analysis

  • Endpoint: /wp-json/epn-settings/v1/update_epn_settings
  • Method: POST
  • Authentication: None required (unauthenticated).
  • Vulnerable Parameter: jsonSettings (JSON-encoded string of settings) and login_key (authentication bypass).
  • Preconditions: The plugin must be active.

3. Code Flow

  1. Route Registration: During rest_api_init, the QR_Code_Settings::execute_settings_api() method calls register_api_update_settings().
  2. Missing Capability Check: register_api_update_settings() (in io/functions-inner.php) defines the route /update_epn_settings with 'permission_callback' => '__return_true', bypassing WordPress's built-in capability system.
  3. Hardcoded Authentication: The callback api_update_settings($param) is invoked. It retrieves $param['login_key'].
  4. Bypass: It compares the provided key to base64_decode('MTIz') (which is "123").
  5. Sink: If the key matches, it takes the jsonSettings parameter, decodes it from JSON, and calls update_option('epn_settings', ...) via update_option().

4. Nonce Acquisition Strategy

This vulnerability does not require a WordPress nonce.
The REST API routes are registered with 'permission_callback' => '__return_true'. While WordPress typically requires a _wpnonce for REST API requests using cookie authentication, unauthenticated requests (those without cookies) proceed directly to the permission_callback. Since the callback always returns true, the internal "authentication" check (the hardcoded login_key) is the only barrier.

5. Exploitation Strategy

The goal is to modify the plugin's settings to prove unauthorized control. We will change the prev_text setting, which is used for navigation links.

  • Target URL: http://<target>/wp-json/epn-settings/v1/update_epn_settings
  • Method: POST
  • Payload Construction:
    • login_key: 123
    • jsonSettings: {"prev_text":"POC_EXPLOITED_SUCCESSFULLY"} (Note: This must be a JSON-encoded string sent as a standard POST parameter).
  • Request Configuration:
    • Content-Type: application/x-www-form-urlencoded
    • Body: login_key=123&jsonSettings=%7B%22prev_text%22%3A%22POC_EXPLOITED_SUCCESSFULLY%22%7D

6. Test Data Setup

  1. Ensure the plugin Endless Posts Navigation version 2.2.9 or lower is installed and activated.
  2. No specific posts or pages are required, as this exploit targets global plugin options (epn_settings).

7. Expected Results

  • The server should return a 200 OK response.
  • The response body should be a JSON object containing "writeSettingsStatus": "done".
    • Verification Logic from io/functions-inner.php:
      if($epn_settings_update == true){
          $update_epn_settings['writeSettingsStatus'] = 'done';
      }
      
  • The WordPress option epn_settings will be updated in the database.

8. Verification Steps

After performing the HTTP request, use WP-CLI to verify the change:

wp option get epn_settings --format=json

Expected Output: The JSON object should contain "prev_text":"POC_EXPLOITED_SUCCESSFULLY".

9. Alternative Approaches

If the update_epn_settings endpoint is somehow blocked, you can verify the authorization bypass via the Read Settings endpoint:

  • Endpoint: /wp-json/epn-settings/v1/read_epn_settings
  • Method: POST
  • Body: login_key=123
  • Result: This will leak the current plugin configuration, confirming the login_key bypass and the missing authorization.

Information Leakage Check:

The endpoint /wp-json/epn-settings/v1/authentication also has permission_callback => __return_true. Sending a qr_hash that matches the option epn_qrcode_hash (if known or set to default) would leak a temporary login_key and the site's bloginfo. However, the hardcoded 123 bypass is more direct.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Endless Posts Navigation plugin for WordPress (<= 2.2.9) contains a missing authorization vulnerability in its REST API implementation. Unauthenticated attackers can read or update plugin settings by exploiting endpoints registered with '__return_true' as a permission callback and bypassing a weak, hardcoded 'login_key' check.

Vulnerable Code

// io/functions-inner.php line 68
	function api_update_settings($param){			

		$login_key = $param['login_key'];		
		if($login_key == base64_decode('MTIz')){
			
			$update_epn_settings = array(

				'writeSettingsStatus' => 'Not OK',

			);


			$epn_settings = $param['jsonSettings'];

			if(!empty($epn_settings)){

				$epn_settings = json_decode($epn_settings, true);

				$epn_settings_update = update_option('epn_settings', sanitize_epn_data($epn_settings));

				if($epn_settings_update == true){

					$update_epn_settings['writeSettingsStatus'] = 'done';

				}

			}

			$res = new WP_REST_Response($update_epn_settings);	

			return $res;

			

		}
		
	}

---

// io/functions-inner.php line 94
	function register_api_update_settings() {		

		register_rest_route( $this->rest_api_url, '/update_epn_settings', array(

		  'methods' => 'POST',

		  'callback' => array($this,'api_update_settings'),
		  
		  'permission_callback' => '__return_true',

		));
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.2.9/inc/epn_settings.php /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.3.0/inc/epn_settings.php
--- /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.2.9/inc/epn_settings.php	2025-04-24 23:50:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.3.0/inc/epn_settings.php	2026-02-05 10:12:46.000000000 +0000
@@ -30,7 +30,7 @@
 			!empty($_POST['epn_settings'])
 	) {
 		
-		if(wp_verify_nonce( $_REQUEST['epn-nonce'], 'epn-basic' )){
+		if(wp_verify_nonce( $_REQUEST['epn-nonce'], 'epn-basic' ) && current_user_can('manage_options')){
 
 			update_option('epn_settings', sanitize_epn_data($_POST['epn_settings']));
 			
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.2.9/io/functions-inner.php /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.3.0/io/functions-inner.php
--- /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.2.9/io/functions-inner.php	2025-04-24 23:50:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/endless-posts-navigation/2.3.0/io/functions-inner.php	2026-02-05 10:12:46.000000000 +0000
@@ -25,7 +25,7 @@
 
 		$login_key = $param['login_key'];
 
-		if($login_key == base64_decode('MTIz')){			
+		//if($login_key == base64_decode('MTIz')){			
 
 			$epn_settings = get_option('epn_settings');
 
@@ -43,7 +43,7 @@
 
 			return $res;
 
-		}
+		//}
 
 			
 
@@ -59,7 +59,9 @@
 
 		  'callback' => array($this, 'api_read_settings'),
 		  
-		  'permission_callback' => '__return_true',
+		  'permission_callback' => function($request){
+										return current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');
+									}
 
 		));
 	}
@@ -67,7 +69,7 @@
 	function api_update_settings($param){			
 
 		$login_key = $param['login_key'];		
-		if($login_key == base64_decode('MTIz')){
+		//if($login_key == base64_decode('MTIz')){
 			
 			$update_epn_settings = array(
 
@@ -98,7 +100,7 @@
 
 			
 
-		}
+		//}
 		
 	}
 
@@ -110,7 +112,10 @@
 
 		  'callback' => array($this,'api_update_settings'),
 		  
-		  'permission_callback' => '__return_true',
+		  'permission_callback' => function($request){
+										return current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');
+									}
+
 
 		));
 	}
@@ -185,7 +190,10 @@
 
 		  'callback' => array($this,'qrhash_authentication_settings'),
 		  
-		  'permission_callback' => '__return_true',
+		  'permission_callback' => function($request){
+										return current_user_can('manage_options') && wp_verify_nonce($request->get_header('X-WP-Nonce'), 'epn-nonce');
+									}
+
 
 		));
 	}

Exploit Outline

The exploit targets the plugin's custom REST API endpoints which are registered without proper capability checks. An unauthenticated attacker can send a POST request to /wp-json/epn-settings/v1/update_epn_settings. The request must include a 'login_key' parameter set to '123' (which matches the plugin's hardcoded check against a base64-encoded string 'MTIz') and a 'jsonSettings' parameter containing a JSON-encoded object of settings to be updated. This payload allows the attacker to overwrite the 'epn_settings' WordPress option, enabling them to change navigation text, enable/disable post titles, or modify sorting behavior across the site.

Check if your site is affected.

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