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