Is python affected by CVE-2024-24576?

CVE-2024-24576 is a recently published vulnerability in the Rust stdlib. The short version is that passing arguments to .bat files on Windows via Command::new("./test.bat").arg(untrusted_string) is actually unsafe in Rust despite the fact that the .arg/.args functions are supposed to do essentially the same thing as passing argv to execve() (so no shell interpolation/escaping shenanigans like with system()).

Quoting from Rust docs:

Note that the argument is not passed through a shell, but given literally to the program. This means that shell syntax like quotes, escaped characters, word splitting, glob patterns, variable substitution, etc. have no effect.

Despite the original CVE talking about Rust, there is a BatBadBut blog post and a vulnerability note VU#123335 claiming that other languages are also affected (including Python). And that, in the case of Python, there would be a documentation update regarding this vulnerability.

The python version of the POC would be something like this.

test.py:

import subprocess

untrusted_string = 'foo" & whoami'
subprocess.run(["test.bat", untrusted_string])

test.bat:

@echo off
echo Argument passed: %1

Note that shell=True is NOT used here, and the “equivalent” POSIX/Unix/Linux code with ./test.sh containing echo $1 does not appear to be vulnerable (as expected).

So Python is indeed affected by this vulnerability?

I find the current behaviour to be extremely surprising (although I understand that realistically Windows is at fault here, not Python). The current documentation even has a couple sections talking about shell=True vs shell=False and argument sequences on Windows that seem to imply that python does try to escape the supplied arguments when shell=False. A user that is not familiar with how cmd.exe is used on Windows when executing .bat files might reasonably assume that the above POC is safe (as it is safe on Linux).


Was this issue discussed anywhere (I wasn’t able to find anything on the CPython github)? What documentation change is the blogpost talking about?

The Rust stdlib is going to “fix” this issue by refusing to run batch files on Windows with arguments that contain quotation marks by default. Should Python consider doing the same?

Yes Python is affected by it. But as in any other language, only code that passes unsanitised user input to the system command line after a call to a batch file is affected by it.

So in the vast majority, of the very small number of cases affected (using Python to run a batch file), the worst that can happen is, the user can hack their own machine.

If your library can run third party input, and pass that to your batch file, on your user’s machine. Then as ever, your users need to be wary of third party input.

Services running on windows, that accept unsanitised user input, and pass them as args to a batch file are the group that can be targeted. In this case I’d be surprised if that’s the only cyber security vulnerability that should be looked into.

While I agree with your sentiment, I’d like to explicitly point out, that my concern here is that the documentation doesn’t make it clear that subprocess.run(["test.bat", untrusted_string]) is unsanitized on Windows, given that the equivalent subprocess.run(["test.sh", untrusted_string]) call is perfectly safe on Linux.

Now, of course, this difference stems from the fact that the way cmd.exe and .bat files interact on Windows is borderline insane, but this doesn’t make this behaviour any more expected. In fact, the subprocess documentation is written in such a way that during my investigation of this CVE, I was at first tricked into thinking that subprocess.run(argv, shell=False) does include special logic for escaping argv on Windows.

Here are some quotes from the documentation:

Providing a sequence of arguments is generally preferred, as it allows the module to take care of any required escaping and quoting of arguments (e.g. to permit spaces in file names).

On Windows, if args is a sequence, it will be converted to a string in a manner described in Converting an argument sequence to a string on Windows.

Converting an argument sequence to a string on Windows

On Windows, an args sequence is converted to a string that can be parsed using the following rules (which correspond to the rules used by the MS C runtime):

  1. Arguments are delimited by white space, which is either a space or a tab.
  2. A string surrounded by double quotation marks is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument.
  3. A double quotation mark preceded by a backslash is interpreted as a literal double quotation mark.
  4. Backslashes are interpreted literally, unless they immediately precede a double quotation mark.
  5. If backslashes immediately precede a double quotation mark, every pair of backslashes is interpreted as a literal backslash. If the number of backslashes is odd, the last backslash escapes the next double quotation mark as described in rule 3.

I don’t think that it is completely unreasonable for a less experienced user to assume that they don’t need to escape untrusted_string in the POC after reading the above text.

In reality, it seems that this escaping logic described in that section is actually unrelated to the .bat + cmd.exe argument parsing shenanigans and is instead only needed due to the CreateProcess API and doesn’t mitigate the .bat + cmd.exe argument parsing craziness.

I agree, that this situation is probably not that common in practice, but I don’t think that we should dismiss it straight away just because running world-facing Python services on Windows machines is uncommon.

3 Likes

Indeed. I’m not saying this shouldn’t be fixed. Far from it. I’m saying that contrary to some press reports and the 10/10 rating, this is not a code 9, red alert, all hands on deck CVE.

I was curious about this at first too, because of precisely the same extract from the docs you’ve quoted. Clearly in the past, Python devs have put considerable thought into parsing args on Windows, and I assumed this base was covered. My understanding is that’s for the MS C Runtime. Whereas the CVE exploits implicitly launched cmd shells.

3 Likes

Rust has been patched by sanitising input in better way. I wish Python will fix it too.