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-apiThe package may also be globally installed on your system, which can be checked by running:
npm list -g @react-native-community/cli-server-apiUnderstanding
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
.desktopfile 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?