# Quasar Framework: Stored/Reflected XSS via unescaped SSR meta tag rendering in getHead()

Quasar SSR's getHead() interpolates useMeta() title/meta values into raw HTML without escaping, letting attacker-controlled content inject script tags executed pre-hydration.

- Payout: Research
- Severity: critical
- Program: Quasar Framework
- Found by: iaohkut-from-NightWolf-Team
- Disclosed: 2026-10-07
- CVE: CVE-2026-106102
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pq96-jpmf-w254
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254
- Tags: area:web domain:oss domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:xss

**Target:** SSR meta tag serializer (ui/src/plugins/meta/Meta.js getHead/injectServerMeta)
**Root cause:** getHead() builds title/meta/link/script tags via plain template-literal interpolation with no HTML-entity or attribute-quote escaping, unlike the client-side apply() which uses DOM setAttribute.
**Steps:**
1. App renders dynamic metadata via useMeta(), e.g. `useMeta(() => ({ title: post.title, meta: { description: { name: 'description', content: post.excerpt } } }))`
2. Attacker sets title to `My Post</title><script>alert(document.cookie)</script>` and description to `"><script>alert(document.domain)</script>`
3. POST /submit with `{"title":"My Post</title><script>alert(document.cookie)</script>","description":"\"><script>alert(document.domain)</script>"}`
4. GET /render/1 returns raw HTML whose <head> contains the literal unescaped `<script>alert(...)</script>` tags
5. Victim browser parses response top-to-bottom and executes injected script before hydration
**How found:** Compared the SSR-only getHead() serializer against the client-side apply() path, which uses document.createElement + setAttribute (auto-escaping); noticed getHead() is a parallel implementation using raw template-literal interpolation that never received equivalent escaping.
**Exploitation:** Attacker influences any text reaching useMeta() (blog title, comment, profile name, CMS field); on every SSR render the payload is emitted verbatim into the raw HTTP <head>, executing in the victim site's origin with access to document.cookie and DOM.
**Insight:** When a framework has separate client-side and SSR implementations of the same rendering logic, audit the SSR path for missing escaping — DOM APIs auto-escape but string concatenation does not, so the SSR twin is where injection lives.

## Proof of concept (copied exactly from the original report)

**POST payload with title and description XSS**

```
POST /submit` with `{"title":"My Post</title><script>alert(document.cookie)</script>","description":"\"><script>alert(document.domain)</script>"}`
```

**Unescaped script tag in SSR head response**

```
<title>My Post</title><script>alert(document.cookie)</script></title><meta name="description" content=""><script>alert(document.domain)</script>" data-qmeta="description">
```

