Title: subprocess.Popen on a Windows batch file always acts as if shell=True
Type: security Stage:
Components: Documentation, Library (Lib), Windows Versions: Python 3.10, Python 3.9, Python 3.8
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: abigail, docs@python, eryksun, paul.moore, steve.dower, tim.golden, zach.ware
Priority: normal Keywords: patch, security_issue

Created on 2018-05-15 11:01 by abigail, last changed 2021-03-01 15:37 by eryksun.

Pull Requests
URL Status Linked Edit
PR 8906 closed sebres, 2018-08-24 19:43
Messages (2)
msg316638 - (view) Author: Abigail (abigail) Date: 2018-05-15 11:01
It's possible to invoke an application without interpreting any of its arguments as shell magic:

>>> print(subprocess.check_output(["C:/testapplication.exe", "foo", "&&", "echo", "%PROGRAMFILES%"]))
Hello world from application! 5 arguments:
Argument 0: 'C:/testapplication.exe'
Argument 1: 'foo'
Argument 2: '&&'
Argument 3: 'echo'
Argument 4: '%PROGRAMFILES%'

But not so for batch scripts:

>>> print(subprocess.check_output(["C:/testscript.bat", "foo", "&&", "echo", "%PROGRAMFILES%"]))
Hello world from script! 2 arguments:
Argument 0: 'C:/testscript.bat'
Argument 1: 'foo'
C:\Program Files

I don't know if this is a fundamental limitation of Windows' batch script processing, or of the Win32 CreateProcess API, but this looks exploitable, as it allows shell injection: the subprocess docs warn about shell injection in a big red box, and promise you'll be safe if you a list of arguments and the default shell=False.

Tested on Python 2.7.15 and Python 3.6.5.
msg316716 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2018-05-15 20:17
There's no simple workaround for this behavior. All we can reasonably do is document that running a batch script directly has the same security risks as using shell=True. 

CMD doesn't support a file argument. It only supports running a /c or /k command, which can include running multiple commands joined by the &, &&, or || operators. CreateProcess thus executes a .bat or .cmd script by prepending "%ComSpec% /c" to the command line. If %ComSpec% isn't defined, it defaults to "%SystemRoot%\System32\cmd.exe /c".

Environment variables in a command can be escaped in most cases by inserting the "^" escape character after the first "%" character. This disrupts matching the variable name (unless a variable name happens to start with "^"). The escape character itself gets skipped as long as it isn't quoted literally.
Date User Action Args
2021-03-01 15:37:34eryksunsetstage: patch review ->
versions: + Python 3.9, Python 3.10, - Python 2.7, Python 3.6, Python 3.7
2018-08-24 19:43:11sebressetkeywords: + patch
stage: needs patch -> patch review
pull_requests: + pull_request8375
2018-05-15 20:17:31eryksunsetassignee: docs@python
components: + Documentation
versions: + Python 3.7, Python 3.8
keywords: + security_issue
nosy: + eryksun, docs@python

messages: + msg316716
stage: needs patch
2018-05-15 11:01:31abigailcreate