| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- #
- # Copyright 2018 Victor Torre
- #
- # SPDX-License-Identifier: Apache-2.0
- import ast
- import bandit
- from bandit.core import issue
- from bandit.core import test_properties as test
- class DeepAssignation:
- def __init__(self, var_name, ignore_nodes=None):
- self.var_name = var_name
- self.ignore_nodes = ignore_nodes
- def is_assigned_in(self, items):
- assigned = []
- for ast_inst in items:
- new_assigned = self.is_assigned(ast_inst)
- if new_assigned:
- if isinstance(new_assigned, (list, tuple)):
- assigned.extend(new_assigned)
- else:
- assigned.append(new_assigned)
- return assigned
- def is_assigned(self, node):
- assigned = False
- if self.ignore_nodes:
- if isinstance(self.ignore_nodes, (list, tuple, object)):
- if isinstance(node, self.ignore_nodes):
- return assigned
- if isinstance(node, ast.Expr):
- assigned = self.is_assigned(node.value)
- elif isinstance(node, ast.FunctionDef):
- for name in node.args.args:
- if isinstance(name, ast.Name):
- if name.id == self.var_name.id:
- # If is param the assignations are not affected
- return assigned
- assigned = self.is_assigned_in(node.body)
- elif isinstance(node, ast.With):
- for withitem in node.items:
- var_id = getattr(withitem.optional_vars, "id", None)
- if var_id == self.var_name.id:
- assigned = node
- else:
- assigned = self.is_assigned_in(node.body)
- elif isinstance(node, ast.Try):
- assigned = []
- assigned.extend(self.is_assigned_in(node.body))
- assigned.extend(self.is_assigned_in(node.handlers))
- assigned.extend(self.is_assigned_in(node.orelse))
- assigned.extend(self.is_assigned_in(node.finalbody))
- elif isinstance(node, ast.ExceptHandler):
- assigned = []
- assigned.extend(self.is_assigned_in(node.body))
- elif isinstance(node, (ast.If, ast.For, ast.While)):
- assigned = []
- assigned.extend(self.is_assigned_in(node.body))
- assigned.extend(self.is_assigned_in(node.orelse))
- elif isinstance(node, ast.AugAssign):
- if isinstance(node.target, ast.Name):
- if node.target.id == self.var_name.id:
- assigned = node.value
- elif isinstance(node, ast.Assign) and node.targets:
- target = node.targets[0]
- if isinstance(target, ast.Name):
- if target.id == self.var_name.id:
- assigned = node.value
- elif isinstance(target, ast.Tuple) and isinstance(
- node.value, ast.Tuple
- ):
- pos = 0
- for name in target.elts:
- if name.id == self.var_name.id:
- assigned = node.value.elts[pos]
- break
- pos += 1
- return assigned
- def evaluate_var(xss_var, parent, until, ignore_nodes=None):
- secure = False
- if isinstance(xss_var, ast.Name):
- if isinstance(parent, ast.FunctionDef):
- for name in parent.args.args:
- if name.arg == xss_var.id:
- return False # Params are not secure
- analyser = DeepAssignation(xss_var, ignore_nodes)
- for node in parent.body:
- if node.lineno >= until:
- break
- to = analyser.is_assigned(node)
- if to:
- if isinstance(to, ast.Str):
- secure = True
- elif isinstance(to, ast.Name):
- secure = evaluate_var(to, parent, to.lineno, ignore_nodes)
- elif isinstance(to, ast.Call):
- secure = evaluate_call(to, parent, ignore_nodes)
- elif isinstance(to, (list, tuple)):
- num_secure = 0
- for some_to in to:
- if isinstance(some_to, ast.Str):
- num_secure += 1
- elif isinstance(some_to, ast.Name):
- if evaluate_var(
- some_to, parent, node.lineno, ignore_nodes
- ):
- num_secure += 1
- else:
- break
- else:
- break
- if num_secure == len(to):
- secure = True
- else:
- secure = False
- break
- else:
- secure = False
- break
- return secure
- def evaluate_call(call, parent, ignore_nodes=None):
- secure = False
- evaluate = False
- if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute):
- if isinstance(call.func.value, ast.Str) and call.func.attr == "format":
- evaluate = True
- if call.keywords:
- evaluate = False # TODO(??) get support for this
- if evaluate:
- args = list(call.args)
- num_secure = 0
- for arg in args:
- if isinstance(arg, ast.Str):
- num_secure += 1
- elif isinstance(arg, ast.Name):
- if evaluate_var(arg, parent, call.lineno, ignore_nodes):
- num_secure += 1
- else:
- break
- elif isinstance(arg, ast.Call):
- if evaluate_call(arg, parent, ignore_nodes):
- num_secure += 1
- else:
- break
- elif isinstance(arg, ast.Starred) and isinstance(
- arg.value, (ast.List, ast.Tuple)
- ):
- args.extend(arg.value.elts)
- num_secure += 1
- else:
- break
- secure = num_secure == len(args)
- return secure
- def transform2call(var):
- if isinstance(var, ast.BinOp):
- is_mod = isinstance(var.op, ast.Mod)
- is_left_str = isinstance(var.left, ast.Str)
- if is_mod and is_left_str:
- new_call = ast.Call()
- new_call.args = []
- new_call.args = []
- new_call.keywords = None
- new_call.lineno = var.lineno
- new_call.func = ast.Attribute()
- new_call.func.value = var.left
- new_call.func.attr = "format"
- if isinstance(var.right, ast.Tuple):
- new_call.args = var.right.elts
- else:
- new_call.args = [var.right]
- return new_call
- def check_risk(node):
- description = "Potential XSS on mark_safe function."
- xss_var = node.args[0]
- secure = False
- if isinstance(xss_var, ast.Name):
- # Check if the var are secure
- parent = node._bandit_parent
- while not isinstance(parent, (ast.Module, ast.FunctionDef)):
- parent = parent._bandit_parent
- is_param = False
- if isinstance(parent, ast.FunctionDef):
- for name in parent.args.args:
- if name.arg == xss_var.id:
- is_param = True
- break
- if not is_param:
- secure = evaluate_var(xss_var, parent, node.lineno)
- elif isinstance(xss_var, ast.Call):
- parent = node._bandit_parent
- while not isinstance(parent, (ast.Module, ast.FunctionDef)):
- parent = parent._bandit_parent
- secure = evaluate_call(xss_var, parent)
- elif isinstance(xss_var, ast.BinOp):
- is_mod = isinstance(xss_var.op, ast.Mod)
- is_left_str = isinstance(xss_var.left, ast.Str)
- if is_mod and is_left_str:
- parent = node._bandit_parent
- while not isinstance(parent, (ast.Module, ast.FunctionDef)):
- parent = parent._bandit_parent
- new_call = transform2call(xss_var)
- secure = evaluate_call(new_call, parent)
- if not secure:
- return bandit.Issue(
- severity=bandit.MEDIUM,
- confidence=bandit.HIGH,
- cwe=issue.Cwe.BASIC_XSS,
- text=description,
- )
- @test.checks("Call")
- @test.test_id("B703")
- def django_mark_safe(context):
- """**B703: Potential XSS on mark_safe function**
- :Example:
- .. code-block:: none
- >> Issue: [B703:django_mark_safe] Potential XSS on mark_safe function.
- Severity: Medium Confidence: High
- CWE: CWE-80 (https://cwe.mitre.org/data/definitions/80.html)
- Location: examples/mark_safe_insecure.py:159:4
- More Info: https://bandit.readthedocs.io/en/latest/plugins/b703_django_mark_safe.html
- 158 str_arg = 'could be insecure'
- 159 safestring.mark_safe(str_arg)
- .. seealso::
- - https://docs.djangoproject.com/en/dev/topics/security/\
- #cross-site-scripting-xss-protection
- - https://docs.djangoproject.com/en/dev/ref/utils/\
- #module-django.utils.safestring
- - https://docs.djangoproject.com/en/dev/ref/utils/\
- #django.utils.html.format_html
- - https://cwe.mitre.org/data/definitions/80.html
- .. versionadded:: 1.5.0
- .. versionchanged:: 1.7.3
- CWE information added
- """ # noqa: E501
- if context.is_module_imported_like("django.utils.safestring"):
- affected_functions = [
- "mark_safe",
- "SafeText",
- "SafeUnicode",
- "SafeString",
- "SafeBytes",
- ]
- if context.call_function_name in affected_functions:
- xss = context.node.args[0]
- if not isinstance(xss, ast.Str):
- return check_risk(context.node)
|