CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2024-3566
9.85.35%

BatBadBut: The Legacy Windows Nightmare That Won't Die

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 10, 2026·8 min read·47 visits

PoC Available

Executive Summary (TL;DR)

Windows implicitly uses 'cmd.exe' to run batch files even when you don't ask for a shell. Most programming languages didn't account for 'cmd.exe's' bizarre parsing rules, allowing attackers to break out of argument quotes and execute commands. CVSS 9.8.

A critical command injection vulnerability affecting multiple programming language runtimes on Windows. It arises from an impedance mismatch between how runtimes escape arguments for process execution and how the Windows operating system implicitly handles batch files via 'cmd.exe'. This flaw turns standard argument passing into arbitrary remote code execution.

The Hook: The Invisible Shell

Picture this: You are a developer. You are writing a secure application in Rust, or Node.js, or PHP. You need to spawn a child process. You are a responsible adult, so you don't use system() or exec(). You use the safe, array-based process spawning functions like Command::new() or child_process.spawn(). You pass arguments as an array, distinct from the command, specifically to avoid command injection. You pat yourself on the back. You are safe.

Then, you deploy on Windows. And suddenly, your 'safe' function isn't just spawning a process. It's inadvertently inviting a chaotic, 30-year-old demon into your memory space. That demon is cmd.exe.

CVE-2024-3566, dubbed 'BatBadBut', isn't a bug in your code, and it's arguably not even a bug in Windows (according to Microsoft). It is a catastrophic misunderstanding between modern programming languages and the ancient, eldritch horror that is Windows batch file execution. When you try to run a file on Windows, and that file happens to end in .bat or .cmd, Windows doesn't just run it. It effectively says, 'Oh, I can't run text files! Let me pass this to cmd.exe for you.'

The problem? The standard library of your favorite programming language formatted your arguments for the Windows Kernel (CreateProcess), not for the command prompt. And those two parsers speak entirely different languages.

The Flaw: A Tale of Two Parsers

To understand why this breaks, you have to understand the Schizophrenia of Windows argument parsing. In the Unix world, arguments are passed as an array of strings. Clean. Simple. In Windows, the kernel takes a single command-line string. It is up to the application to parse that string back into an array. Most executables use the standard Microsoft C Runtime (CRT) rules for this. These rules are generally sane: you wrap strings in double quotes, and if you have a double quote inside the string, you escape it with a backslash (\").

Most language runtimes (Rust, Node, etc.) implement this CRT escaping logic perfectly. They assume that whatever they spawn will follow these rules. But here enters the villain: cmd.exe. The Windows Command Prompt does not follow CRT rules. It has its own 'unique' parsing logic that dates back to the DOS era. It doesn't respect backslashes as escape characters in the same way. It uses the caret (^) for escaping, and it parses special redirection characters (&, |, <, >) aggressively.

So, here is the setup for disaster: Your runtime sees an argument like "&calc.exe. It thinks, 'I need to escape that quote so it stays a string.' It transforms it into \"&calc.exe and wraps it in outer quotes. The final string sent to the OS looks like "\"&calc.exe". The runtime thinks it sent a safe, escaped string.

But because the target is a .bat file, Windows silently invokes cmd.exe. When cmd.exe parses "\"&calc.exe", it sees the first quote (start string), then it sees \ (just a literal backslash, not an escape), and then it sees " (end string). The string is now closed. What follows next is &calc.exe—which cmd.exe interprets as 'run the previous command, AND THEN run Calculator'.

The Code: The Logic Gap

Let's look at the logic gap in a typical runtime before the patch. The standard Windows argument construction generally looks like this pseudo-code:

function build_command_line(args) {
    let cmd_line = "";
    for (let arg of args) {
        // CRT Escaping Rule: Escape quotes with backslash
        let escaped = arg.replace(/"/g, '\\"');
        cmd_line += ` "${escaped}"`;
    }
    return cmd_line;
}

This logic is mathematically correct for 99% of Windows executables. If you run main.exe, it works. But CreateProcess in Windows has a specific behavior: if the executable path ends in .bat or .cmd, and no extension was provided, or if it was provided explicitly, it spins up cmd.exe /c.

The vulnerability exists because the runtime assumes the destination parser is CRT-compliant. It fails to check if the destination is a batch file. The fix, implemented across Rust, Node, and PHP, involves a terrifying amount of complexity. They now have to check if the command being executed ends in .bat or .cmd. If it does, they have to abandon standard escaping and implement a custom, nightmare-fuel escaping logic specifically for cmd.exe.

Here is a visualization of the failure flow:

The Exploit: Popping Calc (and Shells)

Exploiting this requires a specific set of circumstances, but they are more common than you'd think. You need an application running on Windows that allows you to control an argument passed to a process spawning function. Crucially, the process being spawned must resolve to a batch file. This might happen if the developer explicitly calls wrapper.bat, or if they call wrapper and wrapper.bat exists in the system PATH.

Let's assume a vulnerable Node.js application:

const { spawn } = require('child_process');
// User input controls the argument
let userInput = '"&calc.exe'; 
spawn('test.bat', [userInput]);

The exploit payload is deceptively simple. We aren't overflowing buffers on the heap; we are confusing the parser logic. The payload relies on closing the string literal that the runtime tried to create.

The Payload: "&whoami

The Transformation:

  1. Node.js: Sees the quote. Escapes it with \. Result: \"&whoami.
  2. Node.js: Wraps it in outer quotes for safety. Result: "\"&whoami".
  3. Windows: Passes this string to cmd.exe /c test.bat ...
  4. CMD.exe:
    • Reads ": Open quote.
    • Reads \: Literal backslash (CMD doesn't care about backslashes inside quotes usually, but here parsing gets weird with how the start quote interacts).
    • Reads ": Close quote. (The backslash failed to escape this for CMD).
    • Reads &: Command separator.
    • Reads whoami: Executes command.

If you want to be nastier and avoid immediate crashes or syntax errors in the batch file execution, you might pad it or redirect output. But the core concept is using the quote-escaping mismatch to terminate the argument early.

The Impact: Why You Should Care

This is a CVSS 9.8 for a reason. Remote Code Execution (RCE) is the endgame. If an attacker can trigger this, they are running code with the privileges of your web server or application. On Windows, this often means SYSTEM or a highly privileged service account, because let's be honest, Windows permission management is hard and people get lazy.

Consider the scenarios:

  1. CI/CD Pipelines: Many build agents run on Windows and execute scripts based on commit messages or branch names. A malicious branch name could exploit the build runner.
  2. Server-Side Wrappers: Web apps that wrap command-line tools (like ImageMagick or ffmpeg) often use batch files as shims to set up environments. If the web app passes user filenames to that shim, it's game over.
  3. Developer Tools: Tools like npm, cargo, or composer often execute scripts. If a malicious package includes a weirdly named binary or script, it could trigger this during installation.

The cynical reality is that this vulnerability has existed for decades. It was only assigned a CVE recently because security researchers finally convinced the language maintainers that "Windows is just broken" wasn't a valid excuse for ignoring the problem anymore.

The Fix: Abandoning Hope (or Patching)

The mitigation for BatBadBut is messy. Language maintainers (Rust, Node, PHP) have released patches that detect if a command targets a .bat or .cmd file. If so, they apply custom escaping logic. They escape special shell characters (%, !, ^, ", <, >, |, &) manually.

However, this detection is brittle. It relies on knowing the file extension. If you execute a command without an extension, and Windows resolves it to a .bat file via PATH, the runtime might not know in time to apply the escaping. This is why the 'Fix' is only partial.

Actionable Advice for Developers:

  1. Explicit Extensions: Never spawn a process without the extension. Don't call my-script; call my-script.exe. If you must call a batch file, call my-script.bat so the patched runtime detects it.
  2. Avoid Batch Files: Seriously. Just don't use them for entry points handling user data. Use PowerShell (carefully) or compile a Go/Rust wrapper.
  3. Update Runtimes: Ensure you are on Node.js >21.7.2, Rust >1.77.2, etc.
  4. Input Sanitation: Whitelist allowed characters. If you only expect alphanumeric input, enforce it. Don't let & or " anywhere near your process spawning logic.

Remember: On Windows, CreateProcess is a leaky abstraction. Treat it with the suspicion it deserves.

Official Patches

RustRust Security Advisory for CVE-2024-24576
Node.jsNode.js April 2024 Security Releases

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
5.35%
Top 10% most exploited

Affected Systems

Node.js on WindowsRust Standard Library on WindowsPHP on WindowsHaskell Process Library on WindowsApplications executing .bat or .cmd files with user input

Affected Versions Detail

Product
Affected Versions
Fixed Version
Node.js
OpenJS Foundation
< 18.20.218.20.2
Node.js
OpenJS Foundation
< 20.12.220.12.2
Node.js
OpenJS Foundation
< 21.7.221.7.2
Rust
Rust Foundation
< 1.77.21.77.2
PHP
PHP Group
8.x8.3.5
AttributeDetail
CWE IDCWE-78
CVSS v3.19.8 (Critical)
Attack VectorNetwork (Arguments passed remotely)
EPSS Score0.053 (~5.3%)
Exploit StatusPoC Available
KEV ListedNo

MITRE ATT&CK Mapping

T1059.003Command and Scripting Interpreter: Windows Command Shell
Execution
T1202Indirect Command Execution
Defense Evasion
CWE-78
OS Command Injection

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Known Exploits & Detection

Flatt SecurityOriginal research blog post detailing the BatBadBut vulnerability.

Vulnerability Timeline

Initial research published by RyotaK (Flatt Security)
2024-04-09
Node.js, Rust, and PHP release security updates
2024-04-10
Public disclosure and naming of 'BatBadBut'
2024-04-11

References & Sources

  • [1]BatBadBut: You can't securely execute commands on Windows
  • [2]Rust Advisory: BatBadBut
Related Vulnerabilities
CVE-2024-24576CVE-2024-1874CVE-2024-22423HSEC-2024-0003

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.