eric6/Plugins/CheckerPlugins/CodeStyleChecker/Security/Checks/djangoXssVulnerability.py

changeset 7613
382f89c11e27
child 7614
646742c260bd
equal deleted inserted replaced
7612:ca1ce1e0fcff 7613:382f89c11e27
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing checks for potential XSS vulnerability.
8 """
9
10 #
11 # This is a modified version of the one found in the bandit package.
12 #
13 # Original Copyright 2018 Victor Torre
14 #
15 # SPDX-License-Identifier: Apache-2.0
16 #
17
18 import ast
19 import sys
20
21 PY2 = sys.version_info < (3, 0, 0)
22
23
24 def getChecks():
25 """
26 Public method to get a dictionary with checks handled by this module.
27
28 @return dictionary containing checker lists containing checker function and
29 list of codes
30 @rtype dict
31 """
32 return {
33 "Call": [
34 (checkDjangoXssVulnerability, ("S703",)),
35 ],
36 }
37
38
39 def checkDjangoXssVulnerability(reportError, context, config):
40 """
41 Function to check for potential XSS vulnerability.
42
43 @param reportError function to be used to report errors
44 @type func
45 @param context security context object
46 @type SecurityContext
47 @param config dictionary with configuration data
48 @type dict
49 """
50 if context.isModuleImportedLike('django.utils.safestring'):
51 affectedFunctions = [
52 'mark_safe',
53 'SafeText',
54 'SafeUnicode',
55 'SafeString',
56 'SafeBytes'
57 ]
58 if context.callFunctionName in affectedFunctions:
59 xss = context.node.args[0]
60 if not isinstance(xss, ast.Str):
61 checkPotentialRisk(reportError, context.node)
62
63
64 def checkPotentialRisk(reportError, node):
65 """
66 Function to check a given node for a potential XSS vulnerability.
67
68 @param reportError function to be used to report errors
69 @type func
70 @param node node to be checked
71 @type ast.Call
72 """
73 xssVar = node.args[0]
74
75 secure = False
76
77 if isinstance(xssVar, ast.Name):
78 # Check if the var are secure
79 parent = node._securityParent
80 while not isinstance(parent, (ast.Module, ast.FunctionDef)):
81 parent = parent._securityParent
82
83 isParam = False
84 if isinstance(parent, ast.FunctionDef):
85 for name in parent.args.args:
86 argName = name.id if PY2 else name.arg
87 if argName == xssVar.id:
88 isParam = True
89 break
90
91 if not isParam:
92 secure = evaluateVar(xssVar, parent, node.lineno)
93 elif isinstance(xssVar, ast.Call):
94 parent = node._securityParent
95 while not isinstance(parent, (ast.Module, ast.FunctionDef)):
96 parent = parent._securityParent
97 secure = evaluateCall(xssVar, parent)
98 elif isinstance(xssVar, ast.BinOp):
99 isMod = isinstance(xssVar.op, ast.Mod)
100 isLeftStr = isinstance(xssVar.left, ast.Str)
101 if isMod and isLeftStr:
102 parent = node._securityParent
103 while not isinstance(parent, (ast.Module, ast.FunctionDef)):
104 parent = parent._securityParent
105 newCall = transform2call(xssVar)
106 secure = evaluateCall(newCall, parent)
107
108 if not secure:
109 reportError(
110 node.lineno - 1,
111 node.col_offset,
112 "S703",
113 "M",
114 "H"
115 )
116
117
118 # TODO: carry on from here
119 class DeepAssignation(object):
120 """
121 Class to perform a deep analysis of an assign.
122 """
123 def __init__(self, varName, ignoreNodes=None):
124 """
125 Constructor
126
127 @param varName name of the variable
128 @type str
129 @param ignoreNodes list of nodes to ignore
130 @type list of ast.AST
131 """
132 self.__varName = varName
133 self.__ignoreNodes = ignoreNodes
134
135 def isAssignedIn(self, items):
136 """
137 Public method to check, if the variable is assigned to.
138
139 @param items list of nodes to check against
140 @type list of ast.AST
141 @return list of nodes assigned
142 @rtype list of ast.AST
143 """
144 assigned = []
145 for astInst in items:
146 newAssigned = self.isAssigned(astInst)
147 if newAssigned:
148 if isinstance(newAssigned, (list, tuple)):
149 assigned.extend(newAssigned)
150 else:
151 assigned.append(newAssigned)
152
153 return assigned
154
155 def isAssigned(self, node):
156 """
157 Public method to check assignment against a given node.
158
159 @param node node to check against
160 @type ast.AST
161 @return flag indicating an assignement
162 @rtype bool
163 """
164 assigned = False
165 if self.__ignoreNodes:
166 if isinstance(self.__ignoreNodes, (list, tuple, object)):
167 if isinstance(node, self.__ignoreNodes):
168 return assigned
169
170 if isinstance(node, ast.Expr):
171 assigned = self.isAssigned(node.value)
172 elif isinstance(node, ast.FunctionDef):
173 for name in node.args.args:
174 if isinstance(name, ast.Name):
175 if name.id == self.var_name.id:
176 # If is param the assignations are not affected
177 return assigned
178
179 assigned = self.isAssignedIn(node.body)
180 elif isinstance(node, ast.With):
181 if PY2:
182 if node.optional_vars.id == self.__varName.id:
183 assigned = node
184 else:
185 assigned = self.isAssignedIn(node.body)
186 else:
187 for withitem in node.items:
188 varId = getattr(withitem.optional_vars, 'id', None)
189 if varId == self.__varName.id:
190 assigned = node
191 else:
192 assigned = self.isAssignedIn(node.body)
193 elif PY2 and isinstance(node, ast.TryFinally):
194 assigned = []
195 assigned.extend(self.isAssignedIn(node.body))
196 assigned.extend(self.isAssignedIn(node.finalbody))
197 elif PY2 and isinstance(node, ast.TryExcept):
198 assigned = []
199 assigned.extend(self.isAssignedIn(node.body))
200 assigned.extend(self.isAssignedIn(node.handlers))
201 assigned.extend(self.isAssignedIn(node.orelse))
202 elif not PY2 and isinstance(node, ast.Try):
203 assigned = []
204 assigned.extend(self.isAssignedIn(node.body))
205 assigned.extend(self.isAssignedIn(node.handlers))
206 assigned.extend(self.isAssignedIn(node.orelse))
207 assigned.extend(self.isAssignedIn(node.finalbody))
208 elif isinstance(node, ast.ExceptHandler):
209 assigned = []
210 assigned.extend(self.isAssignedIn(node.body))
211 elif isinstance(node, (ast.If, ast.For, ast.While)):
212 assigned = []
213 assigned.extend(self.isAssignedIn(node.body))
214 assigned.extend(self.isAssignedIn(node.orelse))
215 elif isinstance(node, ast.AugAssign):
216 if isinstance(node.target, ast.Name):
217 if node.target.id == self.__varName.id:
218 assigned = node.value
219 elif isinstance(node, ast.Assign) and node.targets:
220 target = node.targets[0]
221 if isinstance(target, ast.Name):
222 if target.id == self.__varName.id:
223 assigned = node.value
224 elif isinstance(target, ast.Tuple):
225 pos = 0
226 for name in target.elts:
227 if name.id == self.__varName.id:
228 assigned = node.value.elts[pos]
229 break
230 pos += 1
231
232 return assigned
233
234
235 def evaluateVar(xssVar, parent, until, ignoreNodes=None):
236 """
237 Function to evaluate a variable node for potential XSS vulnerability.
238
239 @param xssVar variable node to be checked
240 @type ast.Name
241 @param parent parent node
242 @type ast.AST
243 @param until end line number to evaluate variable against
244 @type int
245 @param ignoreNodes list of nodes to ignore
246 @type list of ast.AST
247 @return flag indicating a secure evaluation
248 @rtype bool
249 """
250 secure = False
251 if isinstance(xssVar, ast.Name):
252 if isinstance(parent, ast.FunctionDef):
253 for name in parent.args.args:
254 argName = name.id if PY2 else name.arg
255 if argName == xssVar.id:
256 return False # Params are not secure
257
258 analyser = DeepAssignation(xssVar, ignoreNodes)
259 for node in parent.body:
260 if node.lineno >= until:
261 break
262 to = analyser.isAssigned(node)
263 if to:
264 if isinstance(to, ast.Str):
265 secure = True
266 elif isinstance(to, ast.Name):
267 secure = evaluateVar(
268 to, parent, to.lineno, ignoreNodes)
269 elif isinstance(to, ast.Call):
270 secure = evaluateCall(to, parent, ignoreNodes)
271 elif isinstance(to, (list, tuple)):
272 numSecure = 0
273 for someTo in to:
274 if isinstance(someTo, ast.Str):
275 numSecure += 1
276 elif isinstance(someTo, ast.Name):
277 if evaluateVar(someTo, parent,
278 node.lineno, ignoreNodes):
279 numSecure += 1
280 else:
281 break
282 else:
283 break
284 if numSecure == len(to):
285 secure = True
286 else:
287 secure = False
288 break
289 else:
290 secure = False
291 break
292
293 return secure
294
295
296 def evaluateCall(call, parent, ignoreNodes=None):
297 """
298 Function to evaluate a call node for potential XSS vulnerability.
299
300 @param call call node to be checked
301 @type ast.Call
302 @param parent parent node
303 @type ast.AST
304 @param ignoreNodes list of nodes to ignore
305 @type list of ast.AST
306 @return flag indicating a secure evaluation
307 @rtype bool
308 """
309 secure = False
310 evaluate = False
311
312 if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute):
313 if isinstance(call.func.value, ast.Str) and call.func.attr == 'format':
314 evaluate = True
315 if call.keywords or (PY2 and call.kwargs):
316 evaluate = False
317
318 if evaluate:
319 args = list(call.args)
320 if (
321 PY2 and
322 call.starargs and
323 isinstance(call.starargs, (ast.List, ast.Tuple))
324 ):
325 args.extend(call.starargs.elts)
326
327 numSecure = 0
328 for arg in args:
329 if isinstance(arg, ast.Str):
330 numSecure += 1
331 elif isinstance(arg, ast.Name):
332 if evaluateVar(arg, parent, call.lineno, ignoreNodes):
333 numSecure += 1
334 else:
335 break
336 elif isinstance(arg, ast.Call):
337 if evaluateCall(arg, parent, ignoreNodes):
338 numSecure += 1
339 else:
340 break
341 elif (
342 not PY2 and
343 isinstance(arg, ast.Starred) and
344 isinstance(arg.value, (ast.List, ast.Tuple))
345 ):
346 args.extend(arg.value.elts)
347 numSecure += 1
348 else:
349 break
350 secure = numSecure == len(args)
351
352 return secure
353
354
355 def transform2call(var):
356 """
357 Function to transform a variable node to a call node.
358
359 @param var variable node
360 @type ast.BinOp
361 @return call node
362 @rtype ast.Call
363 """
364 if isinstance(var, ast.BinOp):
365 isMod = isinstance(var.op, ast.Mod)
366 isLeftStr = isinstance(var.left, ast.Str)
367 if isMod and isLeftStr:
368 newCall = ast.Call()
369 newCall.args = []
370 newCall.args = []
371 if PY2:
372 newCall.starargs = None
373 newCall.keywords = None
374 if PY2:
375 newCall.kwargs = None
376 newCall.lineno = var.lineno
377 newCall.func = ast.Attribute()
378 newCall.func.value = var.left
379 newCall.func.attr = 'format'
380 if isinstance(var.right, ast.Tuple):
381 newCall.args = var.right.elts
382 elif PY2 and isinstance(var.right, ast.Dict):
383 newCall.kwargs = var.right
384 else:
385 newCall.args = [var.right]
386
387 return newCall
388
389 return None

eric ide

mercurial