Hostinger Reach <= 1.3.8 - Missing Authorization to Authenticated (Subscriber+) Integration API Key Update
Description
The Hostinger Reach – AI-Powered Email Marketing for WordPress plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the 'handle_ajax_action' function in all versions up to, and including, 1.3.8. This makes it possible for authenticated attackers, with Subscriber-level access and above, to use the 'hostinger_reach_connection_notice_action' action to update the API key value stored in the database. This vulnerability can only be exploited when the plugin is not connected to a site and no API key value exists in the database.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.3.9
Source Code
WordPress.org SVN# Research Plan: CVE-2026-2515 - Hostinger Reach API Key Unauthorized Update ## 1. Vulnerability Summary The **Hostinger Reach** plugin for WordPress (versions <= 1.3.8) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function `handle_ajax_action` (assoc…
Show full research plan
Research Plan: CVE-2026-2515 - Hostinger Reach API Key Unauthorized Update
1. Vulnerability Summary
The Hostinger Reach plugin for WordPress (versions <= 1.3.8) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function handle_ajax_action (associated with the hostinger_reach_connection_notice_action action) fails to perform a capability check (e.g., current_user_can('manage_options')) before updating the plugin's API key in the database.
While the action is protected by a nonce, the nonce is generated and exposed to any authenticated user (including Subscribers) who can access the WordPress admin dashboard where the connection notice is displayed. An attacker can exploit this to inject a malicious API key, potentially hijacking the plugin's integration with Hostinger's email marketing services.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
hostinger_reach_connection_notice_action - Method:
POST - Parameters:
action:hostinger_reach_connection_notice_actionnonce: A valid nonce for the action, found in thehostinger_reach_connection_notice_dataJS object.choice: The payload intended to be saved as the API key (or used to trigger the update).
- Authentication: Authenticated (Subscriber-level access or higher).
- Precondition: The plugin must not already be connected to a site, and the API key option in the database must be empty.
3. Code Flow
- Notice Enqueueing: The plugin enqueues
frontend/dist/connection-notice.jsand localizes it withhostinger_reach_connection_notice_data. - Nonce Exposure: The localization includes a nonce generated via
wp_create_nonce( 'hostinger_reach_connection_notice_action' ). - AJAX Trigger: The JS in
connection-notice.jslistens for clicks on buttons with the class.hostinger-reach-action-button. - Request Formation: When clicked, it sends a
POSTrequest toadmin-ajax.phpwith thechoiceparameter taken from the button'sdata-actionattribute. - Vulnerable Sink: The PHP function
handle_ajax_actionreceives the request:- It calls
check_ajax_referer()orwp_verify_nonce()using the provided nonce. - Crucially, it skips
current_user_can()checks. - It takes the value of
$_POST['choice']and updates a WordPress option (likelyhostinger_reach_api_keyorhostinger_reach_access_token) directly.
- It calls
4. Nonce Acquisition Strategy
The nonce is localized for the connection-notice.js script. Even Subscribers see the admin notice if it is not restricted to administrators.
- Navigate: Login as a Subscriber and navigate to the main dashboard (
/wp-admin/index.php). - Extract: Use
browser_evalto extract the nonce from the global JavaScript object:window.hostinger_reach_connection_notice_data?.nonce - Verify Action: Ensure the
actionmatches the one in the object:window.hostinger_reach_connection_notice_data?.action // Should be "hostinger_reach_connection_notice_action"
5. Exploitation Strategy
- Setup:
- Install Hostinger Reach <= 1.3.8.
- Ensure the plugin is not connected (check that the option
hostinger_reach_api_keydoes not exist or is empty). - Create a Subscriber user.
- Capture Nonce:
- Log in as the Subscriber.
- Access
/wp-admin/. - Run
browser_evalto get the nonce fromhostinger_reach_connection_notice_data.nonce.
- Trigger Exploit:
- Use the
http_requesttool to send a POST request. - Payload:
- URL:
http://localhost:8888/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=hostinger_reach_connection_notice_action&nonce=[NONCE]&choice=pwned_api_key_1337
- URL:
- Use the
- Verification:
- Use WP-CLI to check if the API key was updated in the database.
6. Test Data Setup
- Plugin Version: 1.3.8
- User: Subscriber (
user_login: subscriber,user_pass: subscriber) - Initial State:
wp option delete hostinger_reach_api_key --quiet wp option delete hostinger_reach_connection_data --quiet
7. Expected Results
- The AJAX request should return a JSON success message:
{"success": true, "data": { ... }}. - The
choiceparameter value should be saved into the database. - The response might include a
redirect_urlas seen inconnection-notice.js.
8. Verification Steps
After sending the HTTP request, verify the database state using WP-CLI:
# Check common option names for API keys in this plugin
wp option get hostinger_reach_api_key
# OR
wp option get hostinger_reach_connection_data
If the output is pwned_api_key_1337, the exploit is successful.
9. Alternative Approaches
If the choice parameter is not the direct sink for the API key, it might be that the choice triggers a "Connect" process where the plugin expects a token or key parameter in the same request that isn't used by the JS but is processed by the PHP:
action=hostinger_reach_connection_notice_action&nonce=[NONCE]&choice=connect&api_key=pwned_api_key_1337
Check the PHP handle_ajax_action method for other $_POST or $_REQUEST parameters if choice alone does not work. Given the JS code t.append("choice", e), choice is the primary candidate.
Summary
The Hostinger Reach plugin for WordPress (<= 1.3.8) fails to perform an authorization check in its AJAX handler for connection notices. This allows authenticated attackers, including those with low-level Subscriber permissions, to overwrite the plugin's API key by exploiting a nonce exposed in the admin dashboard.
Vulnerable Code
// frontend/dist/connection-notice.js const e=e=>{ const t=new FormData; t.append("action",hostinger_reach_connection_notice_data.action), t.append("nonce",hostinger_reach_connection_notice_data.nonce), t.append("choice",e), fetch(hostinger_reach_connection_notice_data.ajaxurl,{method:"POST",body:t}) .then(n=>n.json()) .then(e=>{ e.success&&e.data.redirect_url&&(window.location.href=e.data.redirect_url); const t=n().querySelector(".notice-dismiss"); t&&t.click(),notice.style.display="none" }) };
Security Fix
@@ -204,3 +204,13 @@ - Feature: Dependency update - Fix: Send CF7 name field - Dev: Improved automation + +1.3.9 (2026-03-03) + +- Feature: Add main menu entry in Reach +- Fix: Limit Reach to admin users only +- Fix: Expose missed CORS headers +- Tweak: Update translations +- Tweak: Update Readme +- Tweak: Add Add Form Banner +- Tweak: Improve UX for Connection Banner
Exploit Outline
The exploit targets the 'hostinger_reach_connection_notice_action' AJAX action. An attacker with Subscriber-level access logs into the WordPress dashboard and extracts a valid nonce from the global JavaScript object 'hostinger_reach_connection_notice_data'. The attacker then sends a POST request to '/wp-admin/admin-ajax.php' containing the 'action', the stolen 'nonce', and a 'choice' parameter. The 'choice' value is treated as the new API key by the server-side handler which lacks a 'current_user_can()' check, allowing the attacker to hijack the plugin's integration service. This exploitation is effective when the plugin is in an unconnected state.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.