CVE-2026-4146

Loco Translate <= 2.8.2 - Reflected Cross-Site Scripting via 'update_href' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
2.8.3
Patched in
1d
Time to patch

Description

The Loco Translate plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the ‘update_href’ parameter in all versions up to, and including, 2.8.2 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.8.2
PublishedMarch 30, 2026
Last updatedMarch 31, 2026
Affected pluginloco-translate

What Changed in the Fix

Changes introduced in v2.8.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4146 (Loco Translate Reflected XSS) ## 1. Vulnerability Summary The **Loco Translate** plugin (versions <= 2.8.2) is vulnerable to **Reflected Cross-Site Scripting (XSS)** via the `update_href` parameter. The vulnerability exists in the plugin's administration…

Show full research plan

Exploitation Research Plan: CVE-2026-4146 (Loco Translate Reflected XSS)

1. Vulnerability Summary

The Loco Translate plugin (versions <= 2.8.2) is vulnerable to Reflected Cross-Site Scripting (XSS) via the update_href parameter. The vulnerability exists in the plugin's administration settings, specifically within the version/upgrade interface. The plugin fails to sanitize or escape the update_href URL parameter before rendering it into an href attribute of an anchor tag in the administrative dashboard.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin.php
  • Query Parameters:
    • page=loco-config: The main configuration page for Loco Translate.
    • tab=version: The specific tab handled by Loco_admin_config_VersionController.
    • update_href: The payload carrier.
  • Authentication: Required (Administrator). However, the attack is classified as Unauthenticated (CVSS PR:N) because an attacker can craft a malicious link and trick a logged-in administrator into clicking it.
  • Vulnerable Sink: An <a> tag in tpl/admin/config/version.php that echoes the update_href variable without using esc_url().

3. Code Flow

  1. Entry Point: A user (administrator) navigates to wp-admin/admin.php?page=loco-config&tab=version&update_href=javascript:alert(document.domain).
  2. Controller Dispatch: The WordPress admin menu system dispatches the request to Loco Translate's internal router. The tab=version parameter identifies Loco_admin_config_VersionController as the active controller.
  3. Variable Population: The Loco_mvc_Controller (base class) or the VersionController itself populates its internal data view array from the request. In src/admin/config/VersionController.php, the render() method is called.
  4. Conditional Logic:
    • At line 32, the code checks for plugin updates via get_site_transient('update_plugins').
    • If an update is available, setLocoUpdate() (line 70) is called, which calls $this->set('update_href', ...) to define the variable internally.
    • The Flaw: If no update is found, setLocoUpdate() is not called. However, if the update_href parameter is provided in the URL, the underlying MVC framework or the template tpl/admin/config/version.php still perceives the variable as being set (inherited from the $_GET request) and renders the update UI.
  5. Sink: The template tpl/admin/config/version.php (referenced at line 62) outputs the value of update_href inside an href attribute. Since it is not wrapped in esc_url(), the javascript: protocol is allowed to execute.

4. Nonce Acquisition Strategy

This is a Reflected XSS via a GET request to an administrative page.

  • Viewing the loco-config page does not typically require a functional nonce for the initial page load (WordPress relies on standard manage_options capability checks for menu pages).
  • The attack itself is the URL. Once the administrator clicks the crafted link, the XSS executes in their context.
  • No nonce is required to trigger the reflection on the page.

5. Exploitation Strategy

Step-by-Step Plan:

  1. Target Identification: Verify the plugin is installed and version is <= 2.8.2.
  2. Payload Crafting: Create a URL that points to the Loco Translate Version tab with a javascript: payload.
    • Payload: javascript:alert(document.domain)
    • Full URL: /wp-admin/admin.php?page=loco-config&tab=version&update_href=javascript:alert(document.domain)
  3. Execution:
    • Use browser_navigate to visit the crafted URL as an Administrator.
    • The page will render. Even if no update is actually available for Loco Translate, the template logic likely checks if ($update_href) and renders the update link because the parameter is present in the URL.
  4. Trigger:
    • Locate the link in the DOM. Based on the .pot file, it will be near the text "A newer version of Loco Translate is available for download".
    • Use browser_click or browser_eval to click the link.
  5. Verification: Detect the alert box to confirm XSS.

6. Test Data Setup

  1. User: Create an administrator user (e.g., admin/password).
  2. Plugin: Install and activate Loco Translate 2.8.2.
  3. Environment: No specific translations or bundles are needed for this specific XSS, as it resides in the configuration/version view.

7. Expected Results

  • When the Administrator navigates to the malicious URL, a link (or button) intended for "Update" or "Download" will appear on the page.
  • The HTML source for this link will look like: <a href="javascript:alert(document.domain)" ...>...</a>.
  • Clicking the link will trigger a JavaScript alert displaying the site's domain.

8. Verification Steps

  1. Manual Inspection:
    • Use http_request to fetch the page: GET /wp-admin/admin.php?page=loco-config&tab=version&update_href=javascript:alert(1)
    • Search the response body for the string href="javascript:alert(1)".
  2. Automated Trigger:
    • Use browser_navigate to the URL.
    • Execute browser_eval("document.querySelector('a[href^=\"javascript:\"]').click()").
    • Check for the presence of an browser alert.

9. Alternative Approaches

If the update_href is reflected but hidden (e.g., if the "Update" notice requires specific conditions to show), try the following:

  1. Bypass Display Logic: Check if wpupdate_href (referenced as a commented-out candidate in VersionController.php) is also vulnerable.
  2. Attribute Breakout: If the value is reflected in a context where javascript: is blocked but quotes are not escaped, use an attribute breakout:
    • update_href=x" onmouseover="alert(1)"
  3. Transient Mocking: If the link only appears when an update is truly available, use wp_cli to temporarily set a fake update transient:
    wp eval 'set_site_transient("update_plugins", (object)["response" => ["loco-translate/loco.php" => (object)["new_version" => "9.9.9"]]]);'
    
    This will force VersionController::render to call setLocoUpdate(), potentially exposing the sink more reliably.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Loco Translate plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'update_href' parameter in the administrative settings. This occurs because the plugin's MVC framework allows uninitialized template variables to be populated from request parameters, which are then rendered into an anchor tag's href attribute without sanitization.

Vulnerable Code

// src/admin/config/VersionController.php:25
public function render(){
    
    $title = __('Plugin settings','loco-translate');
    $breadcrumb = new Loco_admin_Navigation;
    $breadcrumb->add( $title );
    
    // current plugin version
    $version = loco_plugin_version();
    if( $updates = get_site_transient('update_plugins') ){
        $key = loco_plugin_self();
        if( isset($updates->response[$key]) ){
            $latest = $updates->response[$key]->new_version;
            // if current version is lower than latest, prompt update
            if( version_compare($version,$latest,'<') ){
                $this->setLocoUpdate($latest);
            }
        }
    }
    // ... (truncated)

--- 

// src/admin/config/VersionController.php:69
private function setLocoUpdate( $version ){
    $action = 'upgrade-plugin_'.loco_plugin_self();
    $link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
    $this->set('update', $version );
    $this->set('update_href', wp_nonce_url( $link, $action ) );
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/loco-translate/2.8.2/src/admin/config/VersionController.php
+++ /home/deploy/wp-safety.org/data/plugin-versions/loco-translate/2.8.3/src/admin/config/VersionController.php
@@ -22,6 +22,7 @@
         $title = __('Plugin settings','loco-translate');
         $breadcrumb = new Loco_admin_Navigation;
         $breadcrumb->add( $title );
+        $this->setLocoUpdate('0');
         
         // current plugin version
         $version = loco_plugin_version();
@@ -70,5 +71,11 @@
     private function setLocoUpdate( $version ){
         $action = 'upgrade-plugin_'.loco_plugin_self();
         $link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
-        $this->set('update', $version );
-        $this->set('update_href', wp_nonce_url( $link, $action ) );
+        if ( '0' === $version ) {
+            $this->set('update', '' );
+            $this->set('update_href', '' );
+        }
+        else {
+            $this->set('update', $version );
+            $this->set('update_href', wp_nonce_url( $link, $action ) );
+        }
     }

Exploit Outline

To exploit this vulnerability, an attacker crafts a URL targeting the Loco Translate version settings page: `/wp-admin/admin.php?page=loco-config&tab=version`. The attacker appends a malicious `update_href` parameter containing a `javascript:` payload (e.g., `update_href=javascript:alert(document.domain)`). Because the plugin's MVC pattern defaults to pulling view variables from the global request if they are not explicitly set by the controller, and the 'Version' tab only sets `update_href` when a plugin update is available, the malicious parameter is reflected into the 'Update' link's href attribute. The attacker then tricks a logged-in administrator into visiting the URL and clicking the resulting link to execute the script.

Check if your site is affected.

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