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

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

eric ide

mercurial