CVEReports
Reports
CVEReports

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

Product

  • Home
  • Reports
  • Sitemap
  • RSS Feed

Company

  • About
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Powered by Google Gemini & CVE Feed

|
•

CVE-2026-21449
CVSS 7.4

Bagisto CSTI: When Your Name Becomes Code

Alon Barad
Alon Barad
Software Engineer•January 2, 2026•5 min read
PoC Available

Executive Summary (TL;DR)

Bagisto (an eCommerce platform on Laravel) accidentally allowed Vue.js to hydrate user-controlled input in the customer profile. By changing their name to a Vue template, an attacker can achieve Stored XSS, executing code in the browser of anyone who views that profile—including administrators.

A Client-Side Template Injection (CSTI) vulnerability in the Bagisto eCommerce platform allows authenticated users to execute arbitrary JavaScript by injecting Vue.js templates into profile fields.

The Architecture of a Collision

Modern web development often feels like trying to mix oil and water, or in this case, PHP and JavaScript. Bagisto is built on the 'TALL' stack philosophy adjacent—using Laravel for the backend and heavy doses of Vue.js for the frontend interactivity. While powerful, this combination creates a dangerous intersection known as the 'hydration gap.'

In a standard request, Laravel Blade renders the HTML on the server. It pulls data from the database—like your first name—and slaps it into the DOM. Blade is smart enough to escape HTML entities (<, >, &), preventing standard XSS. It thinks it has done its job. It wipes its hands clean and sends the HTML to the browser.

But here enters Vue.js. When the page loads in the browser, Vue 'hydrates' the DOM, scanning for its own delimiters (usually {{ }). It assumes anything inside those curly braces is trusted logic meant for it to evaluate. The vulnerability exists precisely in this handoff: Blade treats curly braces as text, but Vue treats them as code. If you can get your name saved as {{ 7*7 }}, Blade serves it safely, but Vue executes it.

The Root Cause: Identity Confusion

The root cause isn't a failure of input validation in the traditional sense; it's a failure of context isolation. The application blindly trusted that user input rendered by the server would remain static text on the client. This is a classic Client-Side Template Injection (CSTI).

Technically, the issue resides in packages/Webkul/Shop/src/Resources/views/customers/account/profile/index.blade.php. The developer used the standard Blade echo syntax:

<p class="text-sm font-medium text-zinc-500">
    {{ $customer->first_name }}
</p>

To Blade, $customer->first_name is just a string. If I name myself {{ 7*7 }}, Blade renders <div>{{ 7*7 }}</div>. To the browser, this is valid HTML text content. But because Vue.js is mounted to a parent container of this element, it parses the DOM tree, finds the mustache syntax, and evaluates the mathematical expression inside. The user sees 49 instead of the payload. If the user sees 49, we have code execution.

The Code: Before and After

The fix provided by the Bagisto team in commit 4144931da0014c696f9126132ce44d7cfbdb2761 is a textbook example of how to handle Vue/Blade interoperability, though it feels like a band-aid on a bullet hole.

Here is the vulnerable implementation. Note how the variable is dropped directly into the HTML body:

<!-- VULNERABLE -->
<div class="grid grid-cols-2 gap-y-6">
    <div>
        <p class="text-sm font-medium text-zinc-500">
            {{ $customer->first_name }}
        </p>
    </div>
</div>

The patch forces the data into a Vue directive, specifically v-text. This tells Vue: "Treat the content of this variable strictly as text, never as a template to be parsed."

<!-- FIXED -->
<div class="grid grid-cols-2 gap-y-6">
    <div>
        <p 
            class="text-sm font-medium text-zinc-500"
            v-text="'{{ $customer->first_name }}'"
        >
        </p>
    </div>
</div>

[!NOTE] Notice the wrapping quotes inside v-text="'...'". The Blade engine renders the name inside the single quotes, creating a JavaScript string literal that Vue then assigns to the textContent of the paragraph.

The Exploit: Weaponizing Math

To exploit this, we don't need fancy tools. We just need a browser and a penchant for chaos. The attack vector is the Customer Profile Edit page (/customer/account/profile/edit).

Step 1: The Smoke Test First, we confirm the CSTI. We log in as a low-privileged customer and change our First Name to {{ 7 * 7 }}. Upon saving and viewing the profile, if we see the number 49 rendered on the screen, we have confirmed that the Vue engine is evaluating our input.

Step 2: Escalation Since this is running inside Vue, we have access to the JavaScript context. However, modern Vue versions act as a sandbox. We can't just type alert(1). We need to reach out of the Vue sandbox to the global window object. A typical payload for this environment looks like this:

{{ _v.container.parentElement.ownerDocument.defaultView.alert('Hacked') }}

Step 3: The Impact This is a Stored attack. The payload saves to the database. If an Administrator views the "Customers" list in the backend, and that list uses the same vulnerable Vue rendering (which is common in SPAs/Hybrid apps), the XSS fires in the Admin's session. We steal their cookies, force them to create a new Admin user for us, and take over the shop.

Bypassing the Fix (Theoretical)

The patch uses v-text="'{{ $var }}'". This relies on the assumption that $var won't break out of the single quotes. Blade escapes HTML entities by default, converting ' to &#039;. Vue should handle this correctly by reading the attribute value safely.

However, if there is any point where the HTML is decoded before Vue parses the directive, or if the developer used {!! !!} (raw output) elsewhere, an attacker could inject ' + alert(1) + ' to break the string literal.

While the current patch seems robust against standard attacks due to Blade's default escaping, a more bulletproof approach would be to pass the data via a data-attribute or a proper JSON prop, rather than interpolating strings inside a directive attribute. String interpolation inside code attributes is always one typo away from disaster.

Official Patches

BagistoGitHub Commit fixing the profile XSS

Fix Analysis (1)

Technical Appendix

CVSS Score
7.4/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Affected Systems

Bagisto eCommerce Platform

Affected Versions Detail

ProductAffected VersionsFixed Version
Bagisto
Bagisto
< 2.3.102.3.10
AttributeDetail
CWE IDCWE-1336
Attack VectorNetwork
CVSS v4.07.4
Privileges RequiredLow (Authenticated User)
Exploit StatusPoC Available
ImpactHigh (Confidentiality/Integrity)

MITRE ATT&CK Mapping

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
CWE-1336
Template Injection

Improper Neutralization of Special Elements Used in a Template Engine

Exploit Resources

Known Exploits & Detection

ManualVendor advisory describing the CSTI vector via first_name field.

Vulnerability Timeline

Vulnerability Timeline

Patch Commit Pushed
2026-04-12
GHSA Published
2026-04-15

References & Sources

  • [1]GHSA-mqhg-v22x-pqj8
  • [2]NVD Entry
Related Vulnerabilities
CVE-2026-21450CVE-2026-21448CVE-2026-21451

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.

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.