CVE-2026-54503
Executive Summary
CVE-2026-54503 is a stored cross-site scripting (XSS) vulnerability in plone.app.textfield, the library supplying the RichText field type used across Plone content types and the Plone REST API. Prior to versions 2.0.2, 3.0.2, and 4.0.1 (depending on the release line), RichTextValue.output returns an unsanitized stored value when the value's MIME type equals the configured outputMimeType, bypassing the safe_html transform. An authenticated user able to set a RichText field can store JavaScript that executes in the browser of any viewer, including administrators — yielding session theft, CSRF-token abuse, and privileged actions under the victim's session. CVSS: 4.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N); EPSS 0.00233 (14.4th percentile); no public PoC, no Nuclei template, not in CISA KEV.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Technical Analysis
plone.app.textfield provides RichTextValue, a zope.schema-style value object that couples a text body with a related MIME type. The vulnerability resides in the RichTextValue.output accessor. Under normal operation, when a stored value is requested for rendering, the library is expected to route non-safe content through the safe_html transform, which strips event-handler attributes (e.g., onclick, onerror) and unsafe URI schemes (e.g., javascript:). The transform itself is described as functioning correctly.
The flaw is a type-equality shortcut: when the stored value's MIME type equals the field's outputMimeType, output returns the raw stored value without invoking safe_html. Two paths produce this equality condition:
- Misconfigured or intentionally matched field — a
RichTextfield whosemimeTypeandoutputMimeTypeare both configured to the same value (includingtext/x-html-safe). - REST API input — a request that explicitly supplies
text/x-html-safeas the content type for the stored value, causing the stored MIME type to match the output type.
The resulting attacker-controlled value is then emitted through TAL as tal:content="structure", which interprets the string as markup rather than text and performs no escaping. Because the safe transform is skipped, script-bearing markup survives storage and is rendered into the victim's DOM — a classic stored XSS (CWE-80: Improper Neutralization of Script-Related HTML Tags in a Web Page / Basic XSS). Note that text/x-html-safe is the type used for already-sanitized output; the shortcut effectively trusts a caller-supplied claim of safety instead of re-deriving it. That trust inversion is the root cause.
Attack surface: any Plone object exposing a RichText field the attacker can write to, and the Plone REST API where content type is attacker-influenced. No code execution on the server side is required; the impact is client-side, in the context of a victim's authenticated session. Insufficient public technical detail is available to identify specific endpoint paths, parameter names, or internal function signatures beyond those stated above; the analysis is bounded accordingly.
Exploitation Scenarios
No public PoC is available; the chain below is inferred from the vulnerability class and the NVD description. It uses only identifiers verbatim from that description.
- Reconnaissance / target identification. The attacker identifies a Plone site whose content types expose a
RichTextfield with matchedmimeType/outputMimeType, or whose deployment permits the REST API to accepttext/x-html-safeas the input content type. Fingerprinting a Plone instance is trivial via default assets and headings; no authentication is needed to survey the site. - Triggering the flaw class (stored, type-equality bypass). The attacker obtains an authenticated account with permission to create or edit content containing a
RichTextfield (CVSSPR:L). They submit a stored value whose MIME type equals the output type — either because the field is configured with identicalmimeTypeandoutputMimeType, or by supplyingtext/x-html-safedirectly via REST API input. Because this equality triggers the shortcut,RichTextValue.outputreturns the value raw and thesafe_htmltransform never runs. - Payload / execution (vulnerability-class level). The attacker's stored value includes script-bearing markup (e.g., an event-handler attribute or a
javascript:URI) thatsafe_htmlwould have removed. When a viewer requests the affected content, it is emitted throughtal:content="structure"unescaped and the browser executes the script in the Plone origin. No specific code pattern is asserted beyond standard XSS mechanisms. - Post-exploitation impact. Executing in the victim's browser under the Plone origin enables session-token theft, reading of the victim's CSRF tokens, and state-changing requests as the victim. Targeting an administrative viewer upgrades impact toward full site compromise. The CVSS v3.1 subscores assign Confidentiality:Low, Integrity:None, Availability:None, Scope:Unchanged — the vector captures direct impact conservatively and does not model the escalation achievable via a privileged victim's session.
Impact Assessment
- Primary impact: stored cross-site scripting executing in the Plone origin. Attacker-controlled JavaScript runs in the browser of every user who views the poisoned content, including administrators.
- Direct (CVSS-modeled): Confidentiality:Low; Integrity:None; Availability:None; Scope:Unchanged; Attack Vector:Network; Attack Complexity:Low; Privileges Required:Low; User Interaction:None. Base score 4.3.
- Practical blast radius: a single stored payload can affect all viewers of the content item, so one low-privileged authenticated account can reach a wide audience. Because scripts run in the site context, they can defeat CSRF protections and act as the victim user.
- Affected systems: any deployment pulling a pre-2.0.2, pre-3.0.2, or pre-4.0.1
plone.app.textfieldrelease (the version line depends on the Plone generation). OSV lists affected package records as unfixed in the supplied data, so consumers must confirm the resolved version in their own environment rather than relying on a single advisory. - Exposure context: Plone is commonly internet-facing (content management, intranets, extranets), and the bug is reachable through normal content authoring and the REST API — no exotic access required. EPSS is low (0.00233, 14.4th percentile) and there is no KEV listing or public exploit, so the immediate likelihood of mass exploitation is limited; targeted attacks remain plausible.
Detection Guidance
Because the payload is stored and served to viewers, detection must cover both the write path (content submission) and the read path (rendition of unsafe markup). The rules below are behavioral and class-level, since no exploit traffic is public.
Sigma Rule
title: Potential Plone RichText safe_html Bypass - Unsafe MIME Type Claim (CVE-2026-54503)
id: 7f3c9a21-5b14-4e8d-9c02-1a6e2d545031
status: experimental
description: Detects content submissions that claim text/x-html-safe as the RichText content type, or that carry script-bearing markup, consistent with the CVE-2026-54503 type-equality sanitization bypass.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-54503
author: valtersit.com
date: 2026/01/01
logsource:
category: webserver
detection:
selection_type:
cs-method: ['POST', 'PUT', 'PATCH']
cs-uri-query|contains: '/@@' # plone REST/RPC style content endpoints
selection_payload:
- request_body|contains:
- 'text/x-html-safe'
- 'javascript:'
- 'onerror='
- 'onload='
- '<script'
condition: selection_type and selection_payload
falsepositives:
- Legitimate rich text editors transmitting sanitized markup
- Security tooling that scans for javascript: patterns
level: medium
tags:
- attack.t1189
- cve.2026.54503
YARA Rule
rule CVE_2026_54503_Plone_RichText_XSS
{
meta:
author = "valtersit.com"
description = "Detects stored RichText payloads abusing the text/x-html-safe type-equality shortcut (CVE-2026-54503)"
cve = "CVE-2026-54503"
reference = "https://nvd.nist.gov/vuln/detail/CVE-2026-54503"
date = "2026-01-01"
strings:
$mime = "text/x-html-safe" ascii nocase
$js1 = "javascript:" ascii nocase
$evt1 = "onerror=" ascii nocase
$evt2 = "onload=" ascii nocase
$evt3 = "onclick=" ascii nocase
$scr = "<script" ascii nocase
condition:
uint32(0) != 0x04034b50 and
(
($mime and any of ($js1, $evt*)) or
($mime and $scr)
)
}
Suricata IDS Rule
alert http any any -> any any (msg:"CVE-2026-54503 Plone RichText safe_html Bypass - text/x-html-safe with script markup"; flow:established,to_server; http.method; content:"POST"; http.content_type; content:"text/x-html-safe"; http.request_body; content:"text/x-html-safe"; nocase; pcre:"/(?:javascript:|on(?:error|load|click|mouseover)\s*=)/i"; reference:cve,2026-54503; reference:url,nvd.nist.gov/vuln/detail/CVE-2026-54503; classtype:web-application-attack; sid:5450301; rev:1;)
Elastic Detection
sequence by source.ip with maxspan=1m
[ network where http.request.method in ("POST","PUT","PATCH")
and http.request.body.text/ *"text/x-html-safe"* ]
[ network where http.request.body.text :
("*javascript:*" or "*onerror=*" or "*onload=*" or "*<script*") ]
Splunk SPL Query
index=web sourcetype=access_combined OR sourcetype=plone_access
| eval body = coalesce(request_body, form_data, post_data, "")
| where (method="POST" OR method="PUT" OR method="PATCH")
| where match(body, "(?i)text/x-html-safe") AND (match(body, "(?i)javascript:") OR match(body, "(?i)on(error|load|click|mouseover)\s*=") OR match(body, "(?i)<script"))
| stats count min(_time) as first_seen max(_time) as last_seen by src, uri_path, method, user
| sort - count
| convert ctime(first_seen) ctime(last_seen)
Wazuh OSSEC Rule
<group name="web,plone,xss,cve-2026-54503,">
<rule id="545030" level="10">
<decoded_as>web-accesslog</decoded_as>
<match>text/x-html-safe</match>
<description>CVE-2026-54503: Plone RichText submission claiming text/x-html-safe content type</description>
<group>attack.t1189,pci_dss_6.5.7,</group>
</rule>
<rule id="545031" level="12">
<decoded_as>web-accesslog</decoded_as>
<regex>on(error|load|click|mouseover)\s*=</regex>
<description>CVE-2026-54503: Possible stored XSS payload in RichText submission</description>
<group>attack.t1189,pci_dss_6.5.7,</group>
</rule>
</group>
Tune level and decoded_as to match your deployment's web-accesslog decoder; adjust for the log source that captures request bodies (many access logs do not, in which case enable body logging or WAF telemetry for these rules).
Mitigation & Patch Guidance
Patch confidence: 2/4 (CLAIMED — single source claims patch (not independently confirmed)). Verify against upstream before treating as authoritative.
1) Immediate mitigations
- Disable or restrict REST API access that allows callers to supply
text/x-html-safeas the content type; where possible, validate/reject client-supplied MIME types server-side. - Restrict the ability to create or edit content with
RichTextfields to trusted roles only; the vulnerability requires write access (PR:L). - Deploy a WAF/reverse proxy rule blocking request bodies that claim
text/x-html-safewhile carrying event-handler attributes orjavascript:URIs, and filter response bodies for unescaped script markup. - Audit existing stored content for script-bearing values in
RichTextfields; treat any found as potentially malicious.
2) Patch / upgrade steps
- Upgrade
plone.app.textfieldto 2.0.2, 3.0.2, or 4.0.1, selecting the line matching your Plone generation; do not assume a single version applies to every deployment. - Confirm the resolved version in the running environment (the OSV data lists affected package records as unfixed, so verify locally).
- After upgrade, re-run the
safe_htmlpath on previously stored content to strip any payloads that survived before the fix.
3) Long-term hardening
- Never derive safety from a caller-supplied MIME type; always route
RichTextValue.outputthroughsafe_html(or equivalent) regardless of type equality. - Configure
RichTextfields somimeTypeandoutputMimeTypecannot be set identically in a way that disables sanitization. - Enforce a Content Security Policy and state-changing request CSRF protections so a residual XSS cannot escalate to account or site compromise.
- Add CI checks for XSS regression across
tal:content="structure"sinks.
References
- NVD — CVE-2026-54503: https://nvd.nist.gov/vuln/detail/CVE-2026-54503
- OSV advisory (patch source, single-source claim): https://osv.dev/ (search CVE-2026-54503)
- Plone Security Advisories: https://plone.org/security/advisories
- CWE-80 — Improper Neutralization of Script-Related HTML Tags in a Web Page: https://cwe.mitre.org/data/definitions/80.html
- FIRST EPSS — CVE-2026-54503 score: https://api.first.org/data/v1/epss?cve=CVE-2026-54503
Threat actor campaigns / exploit-in-the-wild: none identified. CISA KEV: not listed. Public PoC: none. Nuclei template: none. No evidence of active exploitation as of this report.
What is this? Security researchers use these exact search queries on Shodan.io (a search engine for internet-connected devices) to find vulnerable systems globally. You can copy these dorks to see if your organization's IP space is exposed.
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.