# Note: Some of these commands will technically not allow an attacker to execute # arbitrary system commands, but only specify the program to be executed. The general # consensus was that even this is still a high security risk, so we also treat them as # system command executions. # # As an example, executing `subprocess.Popen(["rm -rf /"])` will result in # `FileNotFoundError: [Errno 2] No such file or directory: 'rm -rf /'` ######################################## import os # can't use a string literal with spaces in the tags of an InlineExpectationsTest, so using variables :| os.popen("cmd1; cmd2") # $getCommand="cmd1; cmd2" os.system("cmd1; cmd2") # $getCommand="cmd1; cmd2" def os_members(): # hmm, it's kinda annoying to check that we handle this import correctly for # everything. It's quite useful since I messed it up initially and didn't have a # test for it, but in the long run it's just cumbersome to duplicate all the tests # :| from os import popen, system popen("cmd1; cmd2") # $getCommand="cmd1; cmd2" system("cmd1; cmd2") # $getCommand="cmd1; cmd2" ######################################## # https://docs.python.org/3.8/library/os.html#os.execl # # VS Code extension will ignore rest of program if encountering one of these, which we # don't want. We could use `if False`, but just to be 100% sure we don't do anything too # clever in our analysis that discards that code, I used `if UNKNOWN` instead # # below, `path` is an relative/absolute path, for the `p` variants this could also be # the name of a executable, which will be looked up in the PATH environment variable, # which we call `file` to highlight this difference. # # These are also modeled as FileSystemAccess, although they are not super relevant for # the path-injection query -- a user being able to control which program is executed # doesn't sound safe even if that is restricted to be within a certain directory. if UNKNOWN: env = {"FOO": "foo"} os.execl("path", "", "arg0") # $ getCommand="path" getAPathArgument="path" os.execle("path", "", "arg0", env) # $ getCommand="path" getAPathArgument="path" os.execlp("file", "", "arg0") # $ getCommand="file" getAPathArgument="file" os.execlpe("file", "", "arg0", env) # $ getCommand="file" getAPathArgument="file" os.execv("path", ["", "arg0"]) # $ getCommand="path" getAPathArgument="path" os.execve("path", ["", "arg0"], env) # $ getCommand="path" getAPathArgument="path" os.execvp("file", ["", "arg0"]) # $ getCommand="file" getAPathArgument="file" os.execvpe("file", ["", "arg0"], env) # $ getCommand="file" getAPathArgument="file" ######################################## # https://docs.python.org/3.8/library/os.html#os.spawnl env = {"FOO": "foo"} os.spawnl(os.P_WAIT, "path", "", "arg0") # $ getCommand="path" getAPathArgument="path" os.spawnle(os.P_WAIT, "path", "", "arg0", env) # $ getCommand="path" getAPathArgument="path" os.spawnlp(os.P_WAIT, "file", "", "arg0") # $ getCommand="file" getAPathArgument="file" os.spawnlpe(os.P_WAIT, "file", "", "arg0", env) # $ getCommand="file" getAPathArgument="file" os.spawnv(os.P_WAIT, "path", ["", "arg0"]) # $ getCommand="path" getAPathArgument="path" os.spawnve(os.P_WAIT, "path", ["", "arg0"], env) # $ getCommand="path" getAPathArgument="path" os.spawnvp(os.P_WAIT, "file", ["", "arg0"]) # $ getCommand="file" getAPathArgument="file" os.spawnvpe(os.P_WAIT, "file", ["", "arg0"], env) # $ getCommand="file" getAPathArgument="file" # unlike os.exec*, some os.spawn* functions is usable with keyword arguments. However, # despite the docs using both `file` and `path` as the parameter name, you actually need # to use `file` in all cases. os.spawnv(mode=os.P_WAIT, file="path", args=["", "arg0"]) # $ getCommand="path" getAPathArgument="path" os.spawnve(mode=os.P_WAIT, file="path", args=["", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path" os.spawnvp(mode=os.P_WAIT, file="file", args=["", "arg0"]) # $ getCommand="file" getAPathArgument="file" os.spawnvpe(mode=os.P_WAIT, file="file", args=["", "arg0"], env=env) # $ getCommand="file" getAPathArgument="file" # `posix_spawn` Added in Python 3.8 os.posix_spawn("path", ["", "arg0"], env) # $ getCommand="path" getAPathArgument="path" os.posix_spawn(path="path", argv=["", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path" os.posix_spawnp("path", ["", "arg0"], env) # $ getCommand="path" getAPathArgument="path" os.posix_spawnp(path="path", argv=["", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path" ######################################## import subprocess subprocess.Popen("cmd1; cmd2", shell=True) # $getCommand="cmd1; cmd2" subprocess.Popen("cmd1; cmd2", shell="truthy string") # $getCommand="cmd1; cmd2" subprocess.Popen(["cmd1; cmd2", "shell-arg"], shell=True) # $getCommand="cmd1; cmd2" subprocess.Popen("cmd1; cmd2", shell=True, executable="/bin/bash") # $getCommand="cmd1; cmd2" getCommand="/bin/bash" subprocess.Popen("executable") # $getCommand="executable" subprocess.Popen(["executable", "arg0"]) # $getCommand="executable" subprocess.Popen("", executable="executable") # $getCommand="executable" subprocess.Popen(["", "arg0"], executable="executable") # $getCommand="executable" # call/check_call/check_output/run all work like Popen from a command execution point of view subprocess.call(["executable", "arg0"]) # $getCommand="executable" subprocess.check_call(["executable", "arg0"]) # $getCommand="executable" subprocess.check_output(["executable", "arg0"]) # $getCommand="executable" subprocess.run(["executable", "arg0"]) # $getCommand="executable" ######################################## # actively using known shell as the executable subprocess.Popen(["/bin/sh", "-c", "vuln"]) # $getCommand="/bin/sh" MISSING: getCommand="vuln" subprocess.Popen(["/bin/bash", "-c", "vuln"]) # $getCommand="/bin/bash" MISSING: getCommand="vuln" subprocess.Popen(["/bin/dash", "-c", "vuln"]) # $getCommand="/bin/dash" MISSING: getCommand="vuln" subprocess.Popen(["/bin/zsh", "-c", "vuln"]) # $getCommand="/bin/zsh" MISSING: getCommand="vuln" subprocess.Popen(["sh", "-c", "vuln"]) # $getCommand="sh" MISSING: getCommand="vuln" subprocess.Popen(["bash", "-c", "vuln"]) # $getCommand="bash" MISSING: getCommand="vuln" subprocess.Popen(["dash", "-c", "vuln"]) # $getCommand="dash" MISSING: getCommand="vuln" subprocess.Popen(["zsh", "-c", "vuln"]) # $getCommand="zsh" MISSING: getCommand="vuln" # Check that we don't consider ANY argument a command injection sink subprocess.Popen(["sh", "/bin/python"]) # $getCommand="sh" subprocess.Popen(["cmd.exe", "/c", "vuln"]) # $getCommand="cmd.exe" MISSING: getCommand="vuln" subprocess.Popen(["cmd.exe", "/C", "vuln"]) # $getCommand="cmd.exe" MISSING: getCommand="vuln" subprocess.Popen(["cmd", "/c", "vuln"]) # $getCommand="cmd" MISSING: getCommand="vuln" subprocess.Popen(["cmd", "/C", "vuln"]) # $getCommand="cmd" MISSING: getCommand="vuln" subprocess.Popen(["", "-c", "vuln"], executable="/bin/bash") # $getCommand="/bin/bash" MISSING: getCommand="vuln" if UNKNOWN: os.execl("/bin/sh", "", "-c", "vuln") # $getCommand="/bin/sh" getAPathArgument="/bin/sh" MISSING: getCommand="vuln" os.spawnl(os.P_WAIT, "/bin/sh", "", "-c", "vuln") # $getCommand="/bin/sh" getAPathArgument="/bin/sh" MISSING: getCommand="vuln" ######################################## # Passing arguments by reference args = ["/bin/sh", "-c", "vuln"] subprocess.Popen(args) # $getCommand=args args = "" use_shell = False exe = "executable" subprocess.Popen(args, shell=use_shell, executable=exe) # $getCommand=exe ################################################################################ # Taint related import shlex cmd = shlex.join(["echo", tainted]) args = shlex.split(tainted) # will handle tainted = 'foo; rm -rf /' safe_cmd = "ls {}".format(shlex.quote(tainted)) # not how you are supposed to use shlex.quote wrong_use = shlex.quote("ls {}".format(tainted)) # still dangerous, for example cmd = "sh -c " + wrong_use ######################################## # Program/shell command execution via asyncio import asyncio from asyncio import subprocess asyncio.run(asyncio.create_subprocess_exec("executable", "arg0")) # $getCommand="executable" getAPathArgument="executable" asyncio.run(subprocess.create_subprocess_exec("executable", "arg0")) # $getCommand="executable" getAPathArgument="executable" loop = asyncio.new_event_loop() loop.run_until_complete(loop.subprocess_exec(asyncio.SubprocessProtocol, "executable", "arg0")) # $getCommand="executable" getAPathArgument="executable" asyncio.run(asyncio.create_subprocess_shell("shell_command")) # $getCommand="shell_command" getAPathArgument="shell_command" asyncio.run(subprocess.create_subprocess_shell("shell_command")) # $getCommand="shell_command" getAPathArgument="shell_command" loop = asyncio.get_running_loop() loop.run_until_complete(loop.subprocess_shell(asyncio.SubprocessProtocol, "shell_command")) # $getCommand="shell_command" getAPathArgument="shell_command"