Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server-Side Request Forgery Vulnerability (CVE-2024-39338) #6463

Closed
jeffhacks opened this issue Jun 24, 2024 · 24 comments · Fixed by #6539
Closed

Server-Side Request Forgery Vulnerability (CVE-2024-39338) #6463

jeffhacks opened this issue Jun 24, 2024 · 24 comments · Fixed by #6539

Comments

@jeffhacks
Copy link

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 (#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

@jeffhacks jeffhacks changed the title Server-Side Request Forgery Vulnerability Server-Side Request Forgery Vulnerability (CVE-2024-39338) Jul 2, 2024
@pinussilvestrus
Copy link

Is there a fix plan for this? v1.7.3 is not fixing this and it's blocking releasing one of our libraries.

image

@SilverSting
Copy link

SilverSting commented Aug 12, 2024

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

@hainenber
Copy link
Contributor

hainenber commented Aug 12, 2024

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

@nathanloyer
Copy link

Several auditing tools are reporting this issue as of today, including npm audit.

GHSA-8hc4-vh64-cxmj

@furkanmustafa
Copy link

furkanmustafa commented Aug 13, 2024

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?

@rrkoshta123
Copy link

Is there any way that we can fix this issue, as it is showing no patch available.

image

@MikeMcC399
Copy link

MikeMcC399 commented Aug 13, 2024

@rrkoshta123

image

@rrkoshta123
Copy link

rrkoshta123 commented Aug 13, 2024

Hi @MikeMcC399, Thanks for replying.
Let's wait for pull request to be merged.

@petewalker
Copy link

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?) fetch of 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.

@dBiazioli
Copy link

@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...

@jasonsaayman
Copy link
Member

I am looking into it

@jasonsaayman
Copy link
Member

Please check the note in the PR

@levpachmanov
Copy link
Contributor

@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 ?

@hainenber
Copy link
Contributor

hainenber commented Aug 13, 2024

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!

@vernou

This comment was marked as resolved.

@hainenber
Copy link
Contributor

Oh yeah, I accidentally a word there. Thanks for the correction!

@justinmchase
Copy link

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...

Screenshot 2024-08-13 at 12 05 55 PM

Screenshot 2024-08-13 at 12 05 15 PM

@levpachmanov
Copy link
Contributor

Updated the public advisory accordingly - github/advisory-database#4685

@luizbm-abi
Copy link

will audit tools automatically reference this new version as the fixed one?

@Clete2
Copy link

Clete2 commented Aug 13, 2024

will audit tools automatically reference this new version as the fixed one?

Yes. Snyk and GitHub both have it referenced.

@furkanmustafa
Copy link

furkanmustafa commented Aug 14, 2024

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)

@MikeMcC399
Copy link

@justinmchase

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...

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`

@juancarlosjr97
Copy link

@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 :)

@justinmchase
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.