RCE in developer mode in nuxt/nuxt

Valid

Reported on

Apr 27th 2023


Description

Nuxt contains a test-component-wrapper component. This is used to mount a single component for testing.

This component has a dynamic import function which accepts arbitrary user input on the server side. This pattern will almost always lead to an RCE bug.

Requirements & Notes

The server must be running on dev, this does not work in production modes. I'm not entirely sure if this component is included in the production bundle, but this is an incredibly high-risk component. We can use the EMCAScript 'data: import' feature to make payloads easy, this is only possible on nodejs 12+.

Proof of Concept

Start server with pnpm dev.

Navigate to http://localhost:3000/__nuxt_component_test__/?path=data%3Atext%2Fjavascript%2Cconsole%2Elog%28%22hello%21%22%29%3B

Observe hello! in output to console.

Other possible exploits

It may be possible to import a gadget from node_modules or other locations for an exploit if ‘data:’ method is not possible.

Impact

Remote code execution on development servers.

We are processing your report and will contact the nuxt team within 24 hours. 5 months ago
OhB00 modified the report
5 months ago
OhB00 modified the report
5 months ago
OhB00
5 months ago

Researcher


Requires dev mode and probably access to local network, changed to complexity High

OhB00 modified the report
5 months ago
Daniel Roe validated this vulnerability 5 months ago
OhB00 has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
OhB00
5 months ago

Researcher


Fixed in this commit.

Could still be exploitable if:

  • Test environment is exposed to the internet
  • A parser differential is found where resolve(query.path) starts with devRootDir but is interpreted differently to import(query.path). This is unlikely to ever be true but could be resolved by changing the code to:
const path = resolve(query.path as string)
if (!path.startsWith(devRootDir)) {
   throw new Error(`[nuxt] Cannot access path outside of project root directory: \`${path}\`.`)
 }
const comp = await import(/* @vite-ignore */ path as string).then(r => r.default)
OhB00
5 months ago

Researcher


An example of this (which doesn't work) would be http:/nuxt/playground/, if resolve strips the http part, query.path now potentially points to a HTTP resource (this does not work by default in node but for arguments sake) while still passing the test.

OhB00
3 months ago

Researcher


Can this be disclosed?

Daniel Roe marked this as fixed in 3.5.3 with commit 65a8f4 3 months ago
Daniel Roe has been awarded the fix bounty
This vulnerability has been assigned a CVE
Daniel Roe published this vulnerability 3 months ago
OhB00
3 months ago

Researcher


This bug only impacts DEVELOPMENT mode.

Mario
3 months ago

Seems like this vulnerability affects only > v3.4.0 and < v3.4.2. Indeed, can't reproduced it on v2.15.8. npm registry notifies that all nuxt packages < v3.4.3 has a high vulnerability. Feels wrong, isn't it?

# npm audit report

nuxt  <3.4.3
Severity: high
nuxt Code Injection vulnerability - https://github.com/advisories/GHSA-gc34-5v43-h7v8
fix available via `npm audit fix --force`
Will install nuxt@3.5.3, which is a breaking change
node_modules/nuxt
OhB00
3 months ago

Researcher


Seems like GHSA-gc34-5v43-h7v8 is incorrect. Should only impact 3.4.0, 3.4.1, 3.4.2

OhB00
3 months ago

Researcher


Made changes to GHSA-gc34-5v43-h7v8, waiting for GitHub to merge.

OhB00
2 months ago

Researcher


The server being deployed in Development mode is a condition for the vulnerability to work that is well outside the attacker's control.

It is very unlikely an attacker is able to control which mode the server is running in, and will have to be opportunistic to find servers running in this specific mode.

to join this conversation