Native CLI - CVE-2025-11953

The Metro development server (started by @react-native-community/cli) binds to external interfaces by default. Its /open-url endpoint accepts JSON POST request with a url field and passes that value to the open() funtion from the open npm package. Because open() spawns platform-specific OS processes the open the value, a malicious url can result in command execution on some platforms.

Affected: @react-native-community/cli version 4.8.0 --> 20.0.0-alpha.2 Fixed in: 20.0.0 and later

Developers who initiated their React Native project with a vulnerable version of @react-native-community/cli, and run the Metro development server via one of the following or similar commands are vulnerable to CVE-2025-11953:

 npm start
 npm run [start|android|ios|windows|macos]
 npx react-native [start|run-android|run-ios|run-windows|run-macos]
 npx @react-native-community/cli [start|run-android|run-ios|run-windows|run-macos]

The vulnerability directly affects the @react-native-community/cli-server-api package, versions 4.8.0 to 20.0.0-alpha.2, and is fixed since version 20.0.0. Here is how to check whether the vulnerable package exists in a specific NodeJS project:

 cd <Project Folder>
 npm list @react-native-community/cli-server-api

The package may also be globally installed on your system, which can be checked by running:

 npm list -g @react-native-community/cli-server-api

Understanding

The vulnerability is located in the file packages/cli-server-api/src/openURLMiddleware.ts.

import type {IncomingMessage, ServerResponse} from 'http';
import {json} from 'body-parser';
import connect from 'connect';
import open from 'open';

async function openURLMiddleware(
  req: IncomingMessage & {
    // Populated by body-parser
    body?: Object;
  },
  res: ServerResponse,
  next: (err?: Error) => void,
) {
  if (req.method === 'POST') {
    if (req.body == null) {
      res.writeHead(400);
      res.end('Missing request body');
      return;
    }

	// Command Injection Source: 'req.body'
    const {url} = req.body as {url: string};

	// Vulnerability - Command Injection Sink
    await open(url);

    res.writeHead(200);
    res.end();
  }

  next();
}

export default connect().use(json()).use(openURLMiddleware);

The open npm package spawns a child process to open the URL in the system's default application. Because of this the payload needs to be adapted to the victim host system.

Windows On Windows, open() ends up using:

Windows is the easiest to exploit and will interpret the payload as a command via start. Meaning open(url) can almost be seen as exec(url) - it's a subtle but very real OS-specific RCE. Example payloads (from a Linux host):

Linux On Linux, open() uses:

This is safer as it does not run the argument through a shell. Instead the argument is treated as a file path, or a URI and hand it to the OS/desktop to open with the associated program. Instead to get execution on a Unix-like system you could..

  • Point to a local file that the handler will execute.

  • Use a .desktop file that executes when opened. xdb-open file:///... might run it.

  • Find another vulnerable handler like smb://, dav:// etc. that causes execution - this is highly environment-dependent.

macOS On macOS, open() uses:


Mitigation

The patched version of the openURLMiddleware.ts is found here.

  • Upgrade to a patched version (>= 20.0.0)

  • Use --host 127.0.0.1 to bind only to localhost

  • Implement proper input validation and sanitization


References

Last updated

Was this helpful?