CVE-2026-85575
CVSS 6.4 · Medium PatchedCVE-2026-85575: stored XSS in ShopEngine through the product title heading tag
Our Author-role PoC confirmed stored XSS in ShopEngine 4.8.9, from Elementor storage to execution in a visitor’s browser.
For your decision
- Impact
- A widget setting can store JavaScript that executes when a page is viewed.
- Conditions
- Our reproduction uses an Author account on ShopEngine 4.8.9. Wordfence publishes a Contributor+ scope.
- Recommended action
- Update ShopEngine to 4.9.6 or later and review content editable by the affected roles.
Advisory facts
- Package
- wordpress · shopengine
- Affected versions
- ≤ 4.9.5
- Fixed in
- 4.9.6
- CVSS
- 6.4 Medium
- Weakness
- CWE-79
- Access needed
- Account required
- Advisory published
- Credit
- rootzero
Investigation summary
The Product Title widget printed shopengine_product_title_header_size without an allowlist. Our ShopEngine 4.8.9 test proves storage by an Author and execution in a visitor browser; Wordfence describes Contributor+ scope. Fixed in ShopEngine 4.9.6.
From the widget to a visitor’s browser
In our Elementor plugin research, we were following title_tag, header_size and similar settings: a list of tags in the editor is no defence if the server prints the saved value as a tag name. ShopEngine belonged to that series. Its Product Title widget used shopengine_product_title_header_size without an allowlist, while the neighbouring Archive Title widget validated its tag. We used that difference as a control, then prepared a real WooCommerce product and the context this widget needed.
On 31 May 2026 we moved beyond that code comparison: an Author account stored a crafted widget, the page rendered it, and an unauthenticated visitor browser executed it. The separate checks and the WooCommerce setup are described below. Our 1 June Wordfence submission became ticket 456374. ShopEngine released 4.9.6 on 13 September and CVE-2026-85575 followed on the 15th. Wordfence describes Contributor+ scope; our retained replay establishes Author.
How we made the widget render
The initial code difference was narrow: Product Title used the stored shopengine_product_title_header_size as an HTML tag name, while the neighbouring Archive Title widget passed its tag through Helper::esc_options(). That sibling was our negative control. The editor offered a safe dropdown, but the server-side Product Title path did not enforce its options.
A source-code payload alone would have been a weak report. Product Title needs a real WooCommerce product and ShopEngine context before it renders, so we built those fixtures with an administrator account in the lab. The exploit step was separate: a fresh Author account created a published post and stored crafted Elementor JSON through WordPress’s REST API. We read that post back with the same Author session and found the marker in _elementor_data. In the server-rendered page it became an <img> tag with an onerror handler. Finally, Playwright opened the public URL in a new browser context with no WordPress cookies; window.__SHOPENGINE_PT_POC had the expected value 4809 and there was one matching image element. The stored-value check, HTML check and browser check ruled out three different false positives.
This lab proves the Author path on ShopEngine 4.8.9, Elementor 4.1.1 and WooCommerce. Wordfence’s later advisory gives Contributor+ scope. We can cite that assessment, but the preserved browser replay does not independently establish a Contributor save path.
Root cause
The vulnerability is stored cross-site scripting (CWE-79). The heading tag is attacker-chosen input that the widget writes into the markup of the rendered page, and the plugin did not restrict it to an allow-list of heading tags before doing so. The shape is the one shared by this whole family:
Author in our test → sets shopengine_product_title_header_size to a crafted value (not h1..h6)
REST save of Elementor data → stores the value, the client-side dropdown is bypassed
product title widget → prints the value in the tag position, no allow-list
visitor / editor → the browser parses an element the attacker controls
Because the value is written where a tag name goes, a payload does not need angle brackets of its own: a value such as img src=x onerror=alert(document.domain) becomes a real <img> element with an event handler once the plugin emits it after a <. WordPress output filters that strip dangerous markup do not help here, because at save time the value contains no markup to strip.
Exploitation
The Wordfence record lists Contributor or higher, provided the account can edit a page containing this widget. By altering the saved Elementor setting, they set shopengine_product_title_header_size to a payload instead of a heading tag:
shopengine_product_title_header_size = img src=x onerror=alert(document.domain)
The value is stored with the page. The payload runs when someone opens a page that renders the affected widget, including a reviewer previewing the Contributor’s work. In an administrator’s browser, the consequences can extend beyond that page if the script can reach privileged actions and their required nonces; this article does not demonstrate a full-site takeover.
Impact
The CVSS 3.1 score is 6.4, vector AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N: reachable over the network with a low-privilege account. The privilege boundary is the point: a Contributor lacks unfiltered_html and cannot publish their own post, yet a preview opened by an Editor can execute the saved payload.
The fix
ShopEngine 4.9.6, released on 13 September 2026, addresses the reported issue. Its changelog says: “Fixed: Stored Cross-Site Scripting (XSS) vulnerability in the Product-Title-widget.” That entry establishes the fixed version, but does not by itself prove the exact validation performed by the patch. The later 4.9.7 release notes describe broader product-title sanitisation in the same area.
Detection and mitigation
- Update ShopEngine to 4.9.6 or later for the reported issue; use the latest maintained release where possible.
- Review accounts with Contributor or higher access that can edit pages using this widget; remove access that is no longer needed.
- For detection, inspect saved Elementor data for
shopengine_product_title_header_size(and sibling tag settings) holding anything other thanh1toh6ordiv/span. A value containing=, a space orsrcmerits inspection; it alone does not prove exploitation. - The general rule across this family of plugins: a tag chosen in a page-builder is user input, and it must be checked against an allow-list on the server, never trusted because the editor only offered safe options.
Timeline
| Date | Event |
|---|---|
| 2026-05-31 | Local proof on ShopEngine 4.8.9: Author storage, HTML rendering and execution in a visitor browser |
| 2026-06-01 | Wordfence report, ticket 456374 |
| 2026-09-13 | ShopEngine 4.9.6 released with the fix |
| 2026-09-15 | CVE-2026-85575 published, crediting our researcher |
References
- Vendor advisory · CVE-2026-85575 www.wordfence.com/threat-intel/vulnerabilities/id/acd7b1b5-9618-4e8a-b320-bb65978d2c92?source=cve
- NVD entry · CVE-2026-85575 nvd.nist.gov/vuln/detail/CVE-2026-85575
- CVE.org record · CVE-2026-85575 www.cve.org/CVERecord?id=CVE-2026-85575
- MITRE CWE-79 cwe.mitre.org/data/definitions/79.html
- Project repository plugins.trac.wordpress.org/browser/shopengine
Cite this advisory
rootzero. “CVE-2026-85575: stored XSS in ShopEngine through the product title heading tag”. Vulnerability research, rootzero, 15 September 2026. https://rootzero.eu/en/research/cve/cve-2026-85575-shopengine-stored-xss/
#Vulnerability research
Have your code audited before someone else does.
Your team needs to decide what to fix before a release or assess a critical dependency. We scope an engagement around that decision, with evidence and explained remediation priorities.