Endless Posts Navigation <= 2.2.9 - Missing Authorization
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:NTechnical Details
<=2.2.9What Changed in the Fix
Changes introduced in v2.3.0
Source Code
WordPress.org SVNThis 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) andlogin_key(authentication bypass). - Preconditions: The plugin must be active.
3. Code Flow
- Route Registration: During
rest_api_init, theQR_Code_Settings::execute_settings_api()method callsregister_api_update_settings(). - Missing Capability Check:
register_api_update_settings()(inio/functions-inner.php) defines the route/update_epn_settingswith'permission_callback' => '__return_true', bypassing WordPress's built-in capability system. - Hardcoded Authentication: The callback
api_update_settings($param)is invoked. It retrieves$param['login_key']. - Bypass: It compares the provided key to
base64_decode('MTIz')(which is"123"). - Sink: If the key matches, it takes the
jsonSettingsparameter, decodes it from JSON, and callsupdate_option('epn_settings', ...)viaupdate_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:123jsonSettings:{"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
- Content-Type:
6. Test Data Setup
- Ensure the plugin Endless Posts Navigation version 2.2.9 or lower is installed and activated.
- 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 OKresponse. - 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'; }
- Verification Logic from
- The WordPress option
epn_settingswill 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_keybypass 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.
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
@@ -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'])); @@ -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.