src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Security/Checks/injectionShell.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2020 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a check for shell injection.
8 """
9
10 #
11 # This is a modified version of the one found in the bandit package.
12 #
13 # Original Copyright 2014 Hewlett-Packard Development Company, L.P.
14 #
15 # SPDX-License-Identifier: Apache-2.0
16 #
17
18 import ast
19 import re
20
21 import AstUtilities
22
23 from Security.SecurityDefaults import SecurityDefaults
24
25 # This regex starts with a windows drive letter (eg C:)
26 # or one of our path delimeter characters (/, \, .)
27 fullPathMatchRe = re.compile(r'^(?:[A-Za-z](?=\:)|[\\\/\.])')
28
29
30 def getChecks():
31 """
32 Public method to get a dictionary with checks handled by this module.
33
34 @return dictionary containing checker lists containing checker function and
35 list of codes
36 @rtype dict
37 """
38 return {
39 "Call": [
40 (checkSubprocessPopenWithShell, ("S602",)),
41 (checkSubprocessPopenWithoutShell, ("S603",)),
42 (checkOtherFunctionWithShell, ("S604",)),
43 (checkStartProcessWithShell, ("S605",)),
44 (checkStartProcessWithNoShell, ("S606",)),
45 (checkStartProcessWithPartialPath, ("S607",)),
46 ],
47 }
48
49
50 def _evaluateShellCall(context):
51 """
52 Function to determine the severity of a shell call.
53
54 @param context context to be inspected
55 @type SecurityContext
56 @return severity level (L, M or H)
57 @rtype str
58 """
59 noFormatting = AstUtilities.isString(context.node.args[0])
60
61 if noFormatting:
62 return "L"
63 else:
64 return "H"
65
66
67 def hasShell(context):
68 """
69 Function to check, if the node of the context contains the shell keyword.
70
71 @param context context to be inspected
72 @type SecurityContext
73 @return tuple containing a flag indicating the presence of the 'shell'
74 argument and flag indicating the value of the 'shell' argument
75 @rtype tuple of (bool, bool)
76 """
77 keywords = context.node.keywords
78 result = False
79 shell = False
80 if 'shell' in context.callKeywords:
81 shell = True
82 for key in keywords:
83 if key.arg == 'shell':
84 val = key.value
85 if AstUtilities.isNumber(val):
86 result = bool(val.n)
87 elif isinstance(val, ast.List):
88 result = bool(val.elts)
89 elif isinstance(val, ast.Dict):
90 result = bool(val.keys)
91 elif isinstance(val, ast.Name) and val.id in ['False', 'None']:
92 result = False
93 elif AstUtilities.isNameConstant(val):
94 result = val.value
95 else:
96 result = True
97
98 return shell, result
99
100
101 def checkSubprocessPopenWithShell(reportError, context, config):
102 """
103 Function to check for use of popen with shell equals true.
104
105 @param reportError function to be used to report errors
106 @type func
107 @param context security context object
108 @type SecurityContext
109 @param config dictionary with configuration data
110 @type dict
111 """
112 functionNames = (
113 config["shell_injection_subprocess"]
114 if config and "shell_injection_subprocess" in config else
115 SecurityDefaults["shell_injection_subprocess"]
116 )
117
118 if context.callFunctionNameQual in functionNames:
119 shell, shellValue = hasShell(context)
120 if shell and shellValue and len(context.callArgs) > 0:
121 sev = _evaluateShellCall(context)
122 if sev == "L":
123 reportError(
124 context.getLinenoForCallArg('shell') - 1,
125 context.getOffsetForCallArg('shell'),
126 "S602.L",
127 sev,
128 "H",
129 )
130 else:
131 reportError(
132 context.getLinenoForCallArg('shell') - 1,
133 context.getOffsetForCallArg('shell'),
134 "S602.H",
135 sev,
136 "H",
137 )
138
139
140 def checkSubprocessPopenWithoutShell(reportError, context, config):
141 """
142 Function to check for use of popen without shell equals true.
143
144 @param reportError function to be used to report errors
145 @type func
146 @param context security context object
147 @type SecurityContext
148 @param config dictionary with configuration data
149 @type dict
150 """
151 functionNames = (
152 config["shell_injection_subprocess"]
153 if config and "shell_injection_subprocess" in config else
154 SecurityDefaults["shell_injection_subprocess"]
155 )
156
157 if (
158 context.callFunctionNameQual in functionNames and
159 not hasShell(context)[0]
160 ):
161 reportError(
162 context.node.lineno - 1,
163 context.node.col_offset,
164 "S603",
165 "L",
166 "H",
167 )
168
169
170 def checkOtherFunctionWithShell(reportError, context, config):
171 """
172 Function to check for any function with shell equals true.
173
174 @param reportError function to be used to report errors
175 @type func
176 @param context security context object
177 @type SecurityContext
178 @param config dictionary with configuration data
179 @type dict
180 """
181 functionNames = (
182 config["shell_injection_subprocess"]
183 if config and "shell_injection_subprocess" in config else
184 SecurityDefaults["shell_injection_subprocess"]
185 )
186
187 if context.callFunctionNameQual not in functionNames:
188 shell, shellValue = hasShell(context)
189 if shell and shellValue:
190 reportError(
191 context.getLinenoForCallArg('shell') - 1,
192 context.getOffsetForCallArg('shell'),
193 "S604",
194 "M",
195 "L",
196 )
197
198
199 def checkStartProcessWithShell(reportError, context, config):
200 """
201 Function to check for starting a process with a shell.
202
203 @param reportError function to be used to report errors
204 @type func
205 @param context security context object
206 @type SecurityContext
207 @param config dictionary with configuration data
208 @type dict
209 """
210 functionNames = (
211 config["shell_injection_shell"]
212 if config and "shell_injection_shell" in config else
213 SecurityDefaults["shell_injection_shell"]
214 )
215
216 if (
217 context.callFunctionNameQual in functionNames and
218 len(context.callArgs) > 0
219 ):
220 sev = _evaluateShellCall(context)
221 if sev == "L":
222 reportError(
223 context.node.lineno - 1,
224 context.node.col_offset,
225 "S605.L",
226 sev,
227 "H",
228 )
229 else:
230 reportError(
231 context.node.lineno - 1,
232 context.node.col_offset,
233 "S605.H",
234 sev,
235 "H",
236 )
237
238
239 def checkStartProcessWithNoShell(reportError, context, config):
240 """
241 Function to check for starting a process with no shell.
242
243 @param reportError function to be used to report errors
244 @type func
245 @param context security context object
246 @type SecurityContext
247 @param config dictionary with configuration data
248 @type dict
249 """
250 functionNames = (
251 config["shell_injection_noshell"]
252 if config and "shell_injection_noshell" in config else
253 SecurityDefaults["shell_injection_noshell"]
254 )
255
256 if context.callFunctionNameQual in functionNames:
257 reportError(
258 context.node.lineno - 1,
259 context.node.col_offset,
260 "S606",
261 "L",
262 "M",
263 )
264
265
266 def checkStartProcessWithPartialPath(reportError, context, config):
267 """
268 Function to check for starting a process with no shell.
269
270 @param reportError function to be used to report errors
271 @type func
272 @param context security context object
273 @type SecurityContext
274 @param config dictionary with configuration data
275 @type dict
276 """
277 functionNames = (
278 config["shell_injection_subprocess"]
279 if config and "shell_injection_subprocess" in config else
280 SecurityDefaults["shell_injection_subprocess"]
281 )
282
283 if config and "shell_injection_shell" in config:
284 functionNames += config["shell_injection_shell"]
285 else:
286 functionNames += SecurityDefaults["shell_injection_shell"]
287
288 if config and "shell_injection_noshell" in config:
289 functionNames += config["shell_injection_noshell"]
290 else:
291 functionNames += SecurityDefaults["shell_injection_noshell"]
292
293 if (
294 len(context.callArgs) and
295 context.callFunctionNameQual in functionNames
296 ):
297 node = context.node.args[0]
298
299 # some calls take an arg list, check the first part
300 if isinstance(node, ast.List):
301 node = node.elts[0]
302
303 # make sure the param is a string literal and not a var name
304 if (
305 AstUtilities.isString(node) and
306 not fullPathMatchRe.match(node.s)
307 ):
308 reportError(
309 context.node.lineno - 1,
310 context.node.col_offset,
311 "S607",
312 "L",
313 "H",
314 )

eric ide

mercurial