Loco Translate <= 2.8.2 - Reflected Cross-Site Scripting via 'update_href' Parameter
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:NTechnical Details
<=2.8.2What Changed in the Fix
Changes introduced in v2.8.3
Source Code
WordPress.org SVN# 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 byLoco_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 intpl/admin/config/version.phpthat echoes theupdate_hrefvariable without usingesc_url().
3. Code Flow
- Entry Point: A user (administrator) navigates to
wp-admin/admin.php?page=loco-config&tab=version&update_href=javascript:alert(document.domain). - Controller Dispatch: The WordPress admin menu system dispatches the request to Loco Translate's internal router. The
tab=versionparameter identifiesLoco_admin_config_VersionControlleras the active controller. - Variable Population: The
Loco_mvc_Controller(base class) or theVersionControlleritself populates its internal data view array from the request. Insrc/admin/config/VersionController.php, therender()method is called. - 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 theupdate_hrefparameter is provided in the URL, the underlying MVC framework or the templatetpl/admin/config/version.phpstill perceives the variable as being set (inherited from the$_GETrequest) and renders the update UI.
- At line 32, the code checks for plugin updates via
- Sink: The template
tpl/admin/config/version.php(referenced at line 62) outputs the value ofupdate_hrefinside anhrefattribute. Since it is not wrapped inesc_url(), thejavascript: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-configpage does not typically require a functional nonce for the initial page load (WordPress relies on standardmanage_optionscapability 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:
- Target Identification: Verify the plugin is installed and version is <= 2.8.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)
- Payload:
- Execution:
- Use
browser_navigateto 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.
- Use
- Trigger:
- Locate the link in the DOM. Based on the
.potfile, it will be near the text "A newer version of Loco Translate is available for download". - Use
browser_clickorbrowser_evalto click the link.
- Locate the link in the DOM. Based on the
- Verification: Detect the alert box to confirm XSS.
6. Test Data Setup
- User: Create an administrator user (e.g.,
admin/password). - Plugin: Install and activate Loco Translate 2.8.2.
- 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
- Manual Inspection:
- Use
http_requestto 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)".
- Use
- Automated Trigger:
- Use
browser_navigateto the URL. - Execute
browser_eval("document.querySelector('a[href^=\"javascript:\"]').click()"). - Check for the presence of an browser alert.
- Use
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:
- Bypass Display Logic: Check if
wpupdate_href(referenced as a commented-out candidate inVersionController.php) is also vulnerable. - 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)"
- Transient Mocking: If the link only appears when an update is truly available, use
wp_clito temporarily set a fake update transient:
This will forcewp eval 'set_site_transient("update_plugins", (object)["response" => ["loco-translate/loco.php" => (object)["new_version" => "9.9.9"]]]);'VersionController::renderto callsetLocoUpdate(), potentially exposing the sink more reliably.
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
@@ -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.