vuejs.md (10826B)
1 --- 2 title: "Vue.js" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/vuejs.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/vuejs.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Vue.js 14 15 ## XSS Sinks in Vue.js 16 17 ### v-html Directive 18 The `v-html` directive renders **raw** HTML. `<script>` elements inserted through the underlying `innerHTML` operation are normally inert, but active attributes and elements such as an image `onerror` can still produce XSS. Never pass unsanitized user input to `v-html`.<sup>[[1]](#references)[[3]](#references)</sup> 19 20 ```html 21 <div id="app"> 22 <div v-html="htmlContent"></div> 23 </div> 24 <script> 25 new Vue({ 26 el: '#app', 27 data: { 28 htmlContent: '<img src=x onerror=alert(1)>' 29 } 30 }) 31 </script> 32 ``` 33 34 ### v-bind with src or href 35 Binding a user string to URL-bearing attributes (`href`, `src`, `xlink:href`, `formaction` …) lets payloads such as `javascript:alert(1)` run when the link is followed.<sup>[[2]](#references)[[3]](#references)</sup> 36 37 ```html 38 <div id="app"> 39 <a v-bind:href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/userInput">Click me</a> 40 </div> 41 <script> 42 new Vue({ 43 el: '#app', 44 data: { 45 userInput: 'javascript:alert(1)' 46 } 47 }) 48 </script> 49 ``` 50 51 ### v-on with user-controlled handlers 52 If an attacker controls a Vue **template**, directive expressions such as `v-on:click="..."` become code in the compiled render function. Merely storing the string `alert(1)` in a data property does not execute it as JavaScript; the dangerous condition is untrusted template source.<sup>[[3]](#references)</sup> 53 54 ```html 55 <div id="app"> 56 <!-- Dangerous if an attacker controls this template expression: --> 57 <button v-on:click="alert(document.domain)">Click me</button> 58 </div> 59 <script> 60 new Vue({ 61 el: '#app', 62 data: {} 63 }) 64 </script> 65 ``` 66 67 ### Dynamic attribute / event names 68 User-supplied names in `v-bind:[attr]` or `v-on:[event]` expand the attack surface and can bypass application allowlists. Vue applies its own attribute/event handling and CSP still applies, so validate behavior against the deployed Vue version rather than assuming every `on*` name becomes a native handler.<sup>[[3]](#references)</sup> 69 70 ```html 71 <img v-bind:[userAttr]="payload"> 72 <!-- userAttr = 'onerror', payload = 'alert(1)' --> 73 ``` 74 75 ### SSR attribute-name splitting in object `v-bind` 76 77 When auditing SSR, test the **keys** of objects spread with `v-bind="attrs"`, not only their values. `@vue/server-renderer` through 3.5.41 checked dynamic attribute names against a blacklist that omitted carriage return (`U+000D`). A raw CR in an attacker-controlled key therefore reached the HTML response; the browser normalised it to LF and split one intended name into several attributes. This can create a zero-interaction XSS when an object from a CMS, form builder, API or stored JSON is spread onto an element. The published advisory listed no patched version at publication time.<sup>[[9]](#references)</sup> 78 79 ```javascript 80 // Reaches <input v-bind="attrs"> during SSR 81 const attrs = { 82 ['x\rautofocus\ronfocus']: 'alert(document.domain)' 83 } 84 // Browser parses: x="" autofocus="" onfocus="alert(document.domain)" 85 ``` 86 87 This is an **SSR-only** parser differential: client-side Vue uses `setAttribute()`, which rejects such a name. Send the key through the real input path (for example, JSON `"x\rautofocus\ronfocus"`), inspect the raw response bytes before hydration, and verify whether intermediaries preserve the CR. A source-code review should trace both property names and values into `v-bind="object"` or equivalent render-function props.<sup>[[9]](#references)</sup> 88 89 ### Dynamic component (`<component :is>`) 90 Allowing an untrusted component identifier in `:is` may expose components that were not intended for the user. A string containing `<script>` is not automatically compiled as an inline template; server compromise requires a separate path that treats attacker input as template source.<sup>[[3]](#references)</sup> 91 92 ```html 93 <component :is="userChoice"></component> 94 <!-- userChoice must be restricted to an allowlist of intended components --> 95 ``` 96 97 ### Untrusted templates in SSR 98 During server-side rendering, the template runs **on your server**; injecting user HTML can escalate XSS to full Remote Code Execution (RCE). CVEs in `vue-template-compiler` prove the risk.<sup>[[3]](#references)</sup> 99 100 ```javascript 101 // DANGER – never do this 102 const app = createSSRApp({ template: userProvidedHtml }) 103 ``` 104 105 Vue CSTI payloads and version-specific compiler gadgets are documented in [Client Side Template Injection](/hacktricks/pentesting-web/client-side-template-injection-csti); keep this page focused on Vue-specific sinks and SSR boundaries. 106 107 ### Filters / render functions that eval 108 Legacy filters that build render strings or call `eval`/`new Function` on user data are another XSS vector—replace them with computed properties.<sup>[[3]](#references)</sup> 109 110 ```javascript 111 Vue.filter('run', code => eval(code)) // DANGER 112 ``` 113 114 --- 115 116 ## Other Common Vulnerabilities in Vue Projects 117 118 ### Prototype pollution and HTML-context confusion in `vue-i18n` 119 `vue-i18n`'s flat-JSON resolver has accepted `__proto__` paths that write to `Object.prototype`. Trace whether an attacker can supply locale objects or remote translation bundles, then look for polluted properties reaching a secondary gadget rather than treating the write alone as code execution.<sup>[[4]](#references)</sup> 120 121 ```javascript 122 handleFlatJson({ '__proto__.polluted': 'yes' }) 123 console.log(({}).polluted) // "yes" on affected versions 124 ``` 125 126 Do not treat `escapeParameterHtml: true` as a sanitizer for the **final translation string**. Affected `vue-i18n` releases escaped interpolated parameters as text but could still produce executable markup when a translation placed the parameter in an HTML attribute and the result was passed to `v-html`. This is particularly relevant for remotely managed translations and low-privileged locale editors.<sup>[[10]](#references)</sup> 127 128 ```html 129 <!-- message: <img src=x onerror="{payload}"> --> 130 <p v-html="$t('warning', { payload: 'alert(document.domain)' })"></p> 131 ``` 132 133 Audit every `$t()`/`t()` flow into `v-html` and sanitize the complete rendered HTML under an explicit policy. The reviewed advisory identifies 9.14.5, 10.0.8 and 11.1.10 as the first patched release in each maintained branch.<sup>[[10]](#references)</sup> 134 135 ### Open redirects with vue-router 136 Applications sometimes use an unchecked `next` value with `window.location` or an external-navigation helper, creating an open redirect. The earlier page specifically named `router.push`, `<router-link>`, and a `javascript:` destination. Preserve those as test inputs for application wrappers and dynamic URL props, but Vue Router navigation is normally route-oriented, so do not assume the bare router accepts every external scheme without validating the deployed version and normalization path.<sup>[[3]](#references)[[6]](#references)</sup> 137 138 ```javascript 139 window.location.assign(this.$route.query.next) // DANGER: validate origin/scheme 140 ``` 141 142 Prefer a server-side mapping from a short identifier to an approved destination; if a URL must be accepted, apply an allowlist rather than a denylist.<sup>[[6]](#references)</sup> 143 144 ### CSRF in Axios / fetch 145 SPAs still need deliberate CSRF defenses. `SameSite=Lax` or `Strict` blocks cookies on many cross-site requests, but coverage depends on method, navigation context, browser behavior, and whether sibling subdomains are attacker-controlled.<sup>[[7]](#references)</sup> 146 147 ```javascript 148 axios.post('/api/transfer', data, { 149 headers: { 'X-CSRF-TOKEN': token } 150 }) 151 ``` 152 153 ### Click-jacking 154 Prevent framing with CSP `frame-ancestors`; `X-Frame-Options` is a useful legacy fallback. Sending both is defense in depth, not a requirement for standards-compliant modern browsers.<sup>[[8]](#references)</sup> 155 156 ```http 157 X-Frame-Options: DENY 158 Content-Security-Policy: frame-ancestors 'none'; 159 ``` 160 161 ### Content-Security-Policy pitfalls 162 Runtime template compilation can conflict with a strict CSP. Prefer a runtime-only build with precompiled templates so the application does not need eval-like compilation; confirm the exact Vue version and build rather than adding `'unsafe-eval'` blindly.<sup>[[3]](#references)</sup> 163 164 ```http 165 Content-Security-Policy: default-src 'self'; script-src 'self'; 166 ``` 167 168 ### Supply-chain attacks (node-ipc – March 2022) 169 The sabotage of **node-ipc**—pulled by Vue CLI—showed how a transitive dependency can run arbitrary code on dev machines. Pin versions and audit often.<sup>[[5]](#references)</sup> 170 171 ```bash 172 npm ci --ignore-scripts # safer install 173 ``` 174 175 --- 176 177 ## Hardening Checklist 178 179 1. **Sanitise** every string before it hits `v-html` (DOMPurify). 180 2. **Whitelist** allowed schemes, attributes, components, and events; never spread attacker-controlled property names into SSR elements. 181 3. **Avoid `eval`** and dynamic templates altogether. 182 4. **Patch dependencies weekly** and monitor advisories. 183 5. **Send strong HTTP headers** (CSP, HSTS, XFO, CSRF). 184 6. **Lock your supply chain** with audits, lockfiles, and signed commits. 185 186 187 ## References 188 189 - [1] [Vue XSS Guide: Examples and Prevention](https://www.stackhawk.com/blog/vue-xss-guide-examples-and-prevention/) 190 - [2] [Vue JS Security](https://medium.com/@isaacwangethi30/vue-js-security-6e246a7613da) 191 - [3] [Security | Vue.js](https://vuejs.org/guide/best-practices/security) 192 - [4] [Vue I18n Allows Prototype Pollution in handleFlatJson (GHSA-p2ph-7g93-hw3m)](https://github.com/advisories/GHSA-p2ph-7g93-hw3m) 193 - [5] [Alert: peacenotwar module sabotages npm developers in the node-ipc package to protest the invasion of Ukraine](https://snyk.io/blog/peacenotwar-malicious-npm-node-ipc-package-vulnerability/) 194 - [6] [OWASP - Unvalidated Redirects and Forwards Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) 195 - [7] [MDN - Secure cookie configuration and `SameSite`](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies) 196 - [8] [MDN - Clickjacking defenses](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Clickjacking) 197 - [9] [XSS in `@vue/server-renderer` via missing CR in attribute-name blacklist](https://github.com/vuejs/core/security/advisories/GHSA-g2v6-rqmx-r4w6) 198 - [10] [`vue-i18n`'s `escapeParameterHtml` does not prevent DOM-based XSS through tag attributes](https://github.com/advisories/GHSA-x8qp-wqqm-57ph)