-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathSystemCommandExecution.py
More file actions
178 lines (133 loc) · 9.24 KB
/
SystemCommandExecution.py
File metadata and controls
178 lines (133 loc) · 9.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# 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", "<progname>", "arg0") # $ getCommand="path" getAPathArgument="path"
os.execle("path", "<progname>", "arg0", env) # $ getCommand="path" getAPathArgument="path"
os.execlp("file", "<progname>", "arg0") # $ getCommand="file" getAPathArgument="file"
os.execlpe("file", "<progname>", "arg0", env) # $ getCommand="file" getAPathArgument="file"
os.execv("path", ["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
os.execve("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
os.execvp("file", ["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
os.execvpe("file", ["<progname>", "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", "<progname>", "arg0") # $ getCommand="path" getAPathArgument="path"
os.spawnle(os.P_WAIT, "path", "<progname>", "arg0", env) # $ getCommand="path" getAPathArgument="path"
os.spawnlp(os.P_WAIT, "file", "<progname>", "arg0") # $ getCommand="file" getAPathArgument="file"
os.spawnlpe(os.P_WAIT, "file", "<progname>", "arg0", env) # $ getCommand="file" getAPathArgument="file"
os.spawnv(os.P_WAIT, "path", ["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
os.spawnve(os.P_WAIT, "path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
os.spawnvp(os.P_WAIT, "file", ["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
os.spawnvpe(os.P_WAIT, "file", ["<progname>", "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=["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
os.spawnve(mode=os.P_WAIT, file="path", args=["<progname>", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path"
os.spawnvp(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
os.spawnvpe(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"], env=env) # $ getCommand="file" getAPathArgument="file"
# `posix_spawn` Added in Python 3.8
os.posix_spawn("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
os.posix_spawn(path="path", argv=["<progname>", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path"
os.posix_spawnp("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
os.posix_spawnp(path="path", argv=["<progname>", "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("<progname>", executable="executable") # $ getCommand="executable"
subprocess.Popen(["<progname>", "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(["<progname>", "-c", "vuln"], executable="/bin/bash") # $ getCommand="/bin/bash" MISSING: getCommand="vuln"
if UNKNOWN:
os.execl("/bin/sh", "<progname>", "-c", "vuln") # $ getCommand="/bin/sh" getAPathArgument="/bin/sh" MISSING: getCommand="vuln"
os.spawnl(os.P_WAIT, "/bin/sh", "<progname>", "-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 = "<progname>"
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"