diff --git a/bandit/core/blacklisting.py b/bandit/core/blacklisting.py index 2f84ae023..440ec2ed0 100644 --- a/bandit/core/blacklisting.py +++ b/bandit/core/blacklisting.py @@ -2,10 +2,10 @@ # Copyright 2016 Hewlett-Packard Development Company, L.P. # # SPDX-License-Identifier: Apache-2.0 -import ast import fnmatch from bandit.core import issue +from bandit.core import utils def report_issue(check, name): @@ -34,9 +34,9 @@ def blacklist(context, config): if node_type == "Call": func = context.node.func - if isinstance(func, ast.Name) and func.id == "__import__": + if utils.is_instance(func, "Name") and func.id == "__import__": if len(context.node.args): - if isinstance(context.node.args[0], ast.Str): + if utils.is_instance(context.node.args[0], "Str"): name = context.node.args[0].s else: # TODO(??): import through a variable, need symbol tab diff --git a/bandit/core/context.py b/bandit/core/context.py index 801b36466..a02eb961e 100644 --- a/bandit/core/context.py +++ b/bandit/core/context.py @@ -2,8 +2,6 @@ # Copyright 2014 Hewlett-Packard Development Company, L.P. # # SPDX-License-Identifier: Apache-2.0 -import ast - from bandit.core import utils @@ -178,44 +176,44 @@ def _get_literal_value(self, literal): :param literal: The AST literal to convert :return: The value of the AST literal """ - if isinstance(literal, ast.Num): + if utils.is_instance(literal, "Num"): literal_value = literal.n - elif isinstance(literal, ast.Str): + elif utils.is_instance(literal, "Str"): literal_value = literal.s - elif isinstance(literal, ast.List): + elif utils.is_instance(literal, "List"): return_list = list() for li in literal.elts: return_list.append(self._get_literal_value(li)) literal_value = return_list - elif isinstance(literal, ast.Tuple): + elif utils.is_instance(literal, "Tuple"): return_tuple = tuple() for ti in literal.elts: return_tuple = return_tuple + (self._get_literal_value(ti),) literal_value = return_tuple - elif isinstance(literal, ast.Set): + elif utils.is_instance(literal, "Set"): return_set = set() for si in literal.elts: return_set.add(self._get_literal_value(si)) literal_value = return_set - elif isinstance(literal, ast.Dict): + elif utils.is_instance(literal, "Dict"): literal_value = dict(zip(literal.keys, literal.values)) - elif isinstance(literal, ast.Ellipsis): + elif utils.is_instance(literal, "Ellipsis"): # what do we want to do with this? literal_value = None - elif isinstance(literal, ast.Name): + elif utils.is_instance(literal, "Name"): literal_value = literal.id - elif isinstance(literal, ast.NameConstant): + elif utils.is_instance(literal, "NameConstant"): literal_value = str(literal.value) - elif isinstance(literal, ast.Bytes): + elif utils.is_instance(literal, "Bytes"): literal_value = literal.s else: diff --git a/bandit/core/utils.py b/bandit/core/utils.py index 3ac78f54f..307e7f32f 100644 --- a/bandit/core/utils.py +++ b/bandit/core/utils.py @@ -6,6 +6,7 @@ import logging import os.path import sys +from operator import attrgetter try: import configparser @@ -370,3 +371,13 @@ def check_ast_node(name): pass raise TypeError("Error: %s is not a valid node type in AST" % name) + + +def is_instance(node, type_name): + "Check if the given node is an instance AST type." + if isinstance(type_name, tuple): + f = attrgetter(*type_name) + return isinstance(node, f(ast)) + else: + node_type = getattr(ast, type_name) + return isinstance(node, node_type) diff --git a/bandit/plugins/django_sql_injection.py b/bandit/plugins/django_sql_injection.py index 4d5df1aaa..f87d99735 100644 --- a/bandit/plugins/django_sql_injection.py +++ b/bandit/plugins/django_sql_injection.py @@ -2,17 +2,16 @@ # Copyright (C) 2018 [Victor Torre](https://github.com/ehooo) # # SPDX-License-Identifier: Apache-2.0 -import ast - import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils def keywords2dict(keywords): kwargs = {} for node in keywords: - if isinstance(node, ast.keyword): + if utils.is_instance(node, "keyword"): kwargs[node.arg] = node.value return kwargs @@ -66,23 +65,23 @@ def django_extra_used(context): insecure = False for key in ["where", "tables"]: if key in kwargs: - if isinstance(kwargs[key], ast.List): + if utils.is_instance(kwargs[key], "List"): for val in kwargs[key].elts: - if not isinstance(val, ast.Str): + if not utils.is_instance(val, "Str"): insecure = True break else: insecure = True break if not insecure and "select" in kwargs: - if isinstance(kwargs["select"], ast.Dict): + if utils.is_instance(kwargs["select"], "Dict"): for k in kwargs["select"].keys: - if not isinstance(k, ast.Str): + if not utils.is_instance(k, "Str"): insecure = True break if not insecure: for v in kwargs["select"].values: - if not isinstance(v, ast.Str): + if not utils.is_instance(v, "Str"): insecure = True break else: @@ -130,7 +129,7 @@ def django_rawsql_used(context): if context.is_module_imported_like("django.db.models"): if context.call_function_name == "RawSQL": sql = context.node.args[0] - if not isinstance(sql, ast.Str): + if not utils.is_instance(sql, "Str"): return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, diff --git a/bandit/plugins/django_xss.py b/bandit/plugins/django_xss.py index 63a8782b7..a7556b1d6 100644 --- a/bandit/plugins/django_xss.py +++ b/bandit/plugins/django_xss.py @@ -7,6 +7,7 @@ import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils class DeepAssignation: @@ -32,45 +33,45 @@ def is_assigned(self, node): if isinstance(node, self.ignore_nodes): return assigned - if isinstance(node, ast.Expr): + if utils.is_instance(node, "Expr"): assigned = self.is_assigned(node.value) - elif isinstance(node, ast.FunctionDef): + elif utils.is_instance(node, "FunctionDef"): for name in node.args.args: - if isinstance(name, ast.Name): + if utils.is_instance(name, "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): + elif utils.is_instance(node, "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): + elif utils.is_instance(node, "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): + elif utils.is_instance(node, "ExceptHandler"): assigned = [] assigned.extend(self.is_assigned_in(node.body)) - elif isinstance(node, (ast.If, ast.For, ast.While)): + elif utils.is_instance(node, ("If", "For", "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): + elif utils.is_instance(node, "AugAssign"): + if utils.is_instance(node.target, "Name"): if node.target.id == self.var_name.id: assigned = node.value - elif isinstance(node, ast.Assign) and node.targets: + elif utils.is_instance(node, "Assign") and node.targets: target = node.targets[0] - if isinstance(target, ast.Name): + if utils.is_instance(target, "Name"): if target.id == self.var_name.id: assigned = node.value - elif isinstance(target, ast.Tuple): + elif utils.is_instance(target, "Tuple"): pos = 0 for name in target.elts: if name.id == self.var_name.id: @@ -82,8 +83,8 @@ def is_assigned(self, node): def evaluate_var(xss_var, parent, until, ignore_nodes=None): secure = False - if isinstance(xss_var, ast.Name): - if isinstance(parent, ast.FunctionDef): + if utils.is_instance(xss_var, "Name"): + if utils.is_instance(parent, "FunctionDef"): for name in parent.args.args: if name.arg == xss_var.id: return False # Params are not secure @@ -94,18 +95,18 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None): break to = analyser.is_assigned(node) if to: - if isinstance(to, ast.Str): + if utils.is_instance(to, "Str"): secure = True - elif isinstance(to, ast.Name): + elif utils.is_instance(to, "Name"): secure = evaluate_var(to, parent, to.lineno, ignore_nodes) - elif isinstance(to, ast.Call): + elif utils.is_instance(to, "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): + if utils.is_instance(some_to, "Str"): num_secure += 1 - elif isinstance(some_to, ast.Name): + elif utils.is_instance(some_to, "Name"): if evaluate_var( some_to, parent, node.lineno, ignore_nodes ): @@ -128,8 +129,13 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None): 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": + if utils.is_instance(call, "Call") and utils.is_instance( + call.func, "Attribute" + ): + if ( + utils.is_instance(call.func.value, "Str") + and call.func.attr == "format" + ): evaluate = True if call.keywords: evaluate = False # TODO(??) get support for this @@ -138,20 +144,20 @@ def evaluate_call(call, parent, ignore_nodes=None): args = list(call.args) num_secure = 0 for arg in args: - if isinstance(arg, ast.Str): + if utils.is_instance(arg, "Str"): num_secure += 1 - elif isinstance(arg, ast.Name): + elif utils.is_instance(arg, "Name"): if evaluate_var(arg, parent, call.lineno, ignore_nodes): num_secure += 1 else: break - elif isinstance(arg, ast.Call): + elif utils.is_instance(arg, "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) + elif utils.is_instance(arg, "Starred") and utils.is_instance( + arg.value, ("List", "Tuple") ): args.extend(arg.value.elts) num_secure += 1 @@ -163,9 +169,9 @@ def evaluate_call(call, parent, ignore_nodes=None): def transform2call(var): - if isinstance(var, ast.BinOp): - is_mod = isinstance(var.op, ast.Mod) - is_left_str = isinstance(var.left, ast.Str) + if utils.is_instance(var, "BinOp"): + is_mod = utils.is_instance(var.op, "Mod") + is_left_str = utils.is_instance(var.left, "Str") if is_mod and is_left_str: new_call = ast.Call() new_call.args = [] @@ -175,7 +181,7 @@ def transform2call(var): new_call.func = ast.Attribute() new_call.func.value = var.left new_call.func.attr = "format" - if isinstance(var.right, ast.Tuple): + if utils.is_instance(var.right, "Tuple"): new_call.args = var.right.elts else: new_call.args = [var.right] @@ -188,14 +194,14 @@ def check_risk(node): secure = False - if isinstance(xss_var, ast.Name): + if utils.is_instance(xss_var, "Name"): # Check if the var are secure parent = node._bandit_parent - while not isinstance(parent, (ast.Module, ast.FunctionDef)): + while not utils.is_instance(parent, ("Module", "FunctionDef")): parent = parent._bandit_parent is_param = False - if isinstance(parent, ast.FunctionDef): + if utils.is_instance(parent, "FunctionDef"): for name in parent.args.args: if name.arg == xss_var.id: is_param = True @@ -203,17 +209,17 @@ def check_risk(node): if not is_param: secure = evaluate_var(xss_var, parent, node.lineno) - elif isinstance(xss_var, ast.Call): + elif utils.is_instance(xss_var, "Call"): parent = node._bandit_parent - while not isinstance(parent, (ast.Module, ast.FunctionDef)): + while not utils.is_instance(parent, ("Module", "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) + elif utils.is_instance(xss_var, "BinOp"): + is_mod = utils.is_instance(xss_var.op, "Mod") + is_left_str = utils.is_instance(xss_var.left, "Str") if is_mod and is_left_str: parent = node._bandit_parent - while not isinstance(parent, (ast.Module, ast.FunctionDef)): + while not utils.is_instance(parent, ("Module", "FunctionDef")): parent = parent._bandit_parent new_call = transform2call(xss_var) secure = evaluate_call(new_call, parent) @@ -270,5 +276,5 @@ def django_mark_safe(context): ] if context.call_function_name in affected_functions: xss = context.node.args[0] - if not isinstance(xss, ast.Str): + if not utils.is_instance(xss, "Str"): return check_risk(context.node) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 9a162b485..508099753 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -2,12 +2,12 @@ # Copyright 2014 Hewlett-Packard Development Company, L.P. # # SPDX-License-Identifier: Apache-2.0 -import ast import re import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils RE_WORDS = "(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)" @@ -80,48 +80,50 @@ def hardcoded_password_string(context): """ node = context.node - if isinstance(node._bandit_parent, ast.Assign): + if utils.is_instance(node._bandit_parent, "Assign"): # looks for "candidate='some_string'" for targ in node._bandit_parent.targets: - if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id): + if utils.is_instance(targ, "Name") and RE_CANDIDATES.search( + targ.id + ): return _report(node.s) - elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search( + elif utils.is_instance(targ, "Attribute") and RE_CANDIDATES.search( targ.attr ): return _report(node.s) - elif isinstance( - node._bandit_parent, ast.Subscript + elif utils.is_instance( + node._bandit_parent, "Subscript" ) and RE_CANDIDATES.search(node.s): # Py39+: looks for "dict[candidate]='some_string'" # subscript -> index -> string assign = node._bandit_parent._bandit_parent - if isinstance(assign, ast.Assign) and isinstance( - assign.value, ast.Str + if utils.is_instance(assign, "Assign") and utils.is_instance( + assign.value, "Str" ): return _report(assign.value.s) - elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search( - node.s - ): + elif utils.is_instance( + node._bandit_parent, "Index" + ) and RE_CANDIDATES.search(node.s): # looks for "dict[candidate]='some_string'" # assign -> subscript -> index -> string assign = node._bandit_parent._bandit_parent._bandit_parent - if isinstance(assign, ast.Assign) and isinstance( - assign.value, ast.Str + if utils.is_instance(assign, "Assign") and utils.is_instance( + assign.value, "Str" ): return _report(assign.value.s) - elif isinstance(node._bandit_parent, ast.Compare): + elif utils.is_instance(node._bandit_parent, "Compare"): # looks for "candidate == 'some_string'" comp = node._bandit_parent - if isinstance(comp.left, ast.Name): + if utils.is_instance(comp.left, "Name"): if RE_CANDIDATES.search(comp.left.id): - if isinstance(comp.comparators[0], ast.Str): + if utils.is_instance(comp.comparators[0], "Str"): return _report(comp.comparators[0].s) - elif isinstance(comp.left, ast.Attribute): + elif utils.is_instance(comp.left, "Attribute"): if RE_CANDIDATES.search(comp.left.attr): - if isinstance(comp.comparators[0], ast.Str): + if utils.is_instance(comp.comparators[0], "Str"): return _report(comp.comparators[0].s) @@ -177,7 +179,7 @@ def hardcoded_password_funcarg(context): """ # looks for "function(candidate='some_string')" for kw in context.node.keywords: - if isinstance(kw.value, ast.Str) and RE_CANDIDATES.search(kw.arg): + if utils.is_instance(kw.value, "Str") and RE_CANDIDATES.search(kw.arg): return _report(kw.value.s) @@ -242,6 +244,6 @@ def hardcoded_password_default(context): # go through all (param, value)s and look for candidates for key, val in zip(context.node.args.args, defs): - if isinstance(key, (ast.Name, ast.arg)): - if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg): + if utils.is_instance(key, ("Name", "arg")): + if utils.is_instance(val, "Str") and RE_CANDIDATES.search(key.arg): return _report(val.s) diff --git a/bandit/plugins/injection_shell.py b/bandit/plugins/injection_shell.py index 19c31d209..a741bb73a 100644 --- a/bandit/plugins/injection_shell.py +++ b/bandit/plugins/injection_shell.py @@ -2,12 +2,12 @@ # Copyright 2014 Hewlett-Packard Development Company, L.P. # # SPDX-License-Identifier: Apache-2.0 -import ast import re import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils # yuck, regex: starts with a windows drive letter (eg C:) @@ -16,7 +16,7 @@ def _evaluate_shell_call(context): - no_formatting = isinstance(context.node.args[0], ast.Str) + no_formatting = utils.is_instance(context.node.args[0], "Str") if no_formatting: return bandit.LOW @@ -82,15 +82,18 @@ def has_shell(context): for key in keywords: if key.arg == "shell": val = key.value - if isinstance(val, ast.Num): + if utils.is_instance(val, "Num"): result = bool(val.n) - elif isinstance(val, ast.List): + elif utils.is_instance(val, "List"): result = bool(val.elts) - elif isinstance(val, ast.Dict): + elif utils.is_instance(val, "Dict"): result = bool(val.keys) - elif isinstance(val, ast.Name) and val.id in ["False", "None"]: + elif utils.is_instance(val, "Name") and val.id in [ + "False", + "None", + ]: result = False - elif isinstance(val, ast.NameConstant): + elif utils.is_instance(val, "NameConstant"): result = val.value else: result = True @@ -681,11 +684,13 @@ def start_process_with_partial_path(context, config): node = context.node.args[0] # some calls take an arg list, check the first part - if isinstance(node, ast.List): + if utils.is_instance(node, "List"): node = node.elts[0] # make sure the param is a string literal and not a var name - if isinstance(node, ast.Str) and not full_path_match.match(node.s): + if utils.is_instance(node, "Str") and not full_path_match.match( + node.s + ): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH, diff --git a/bandit/plugins/injection_sql.py b/bandit/plugins/injection_sql.py index c69750ca1..cee419f2a 100644 --- a/bandit/plugins/injection_sql.py +++ b/bandit/plugins/injection_sql.py @@ -53,7 +53,6 @@ CWE information added """ # noqa: E501 -import ast import re import bandit @@ -61,6 +60,7 @@ from bandit.core import test_properties as test from bandit.core import utils + SIMPLE_SQL_RE = re.compile( r"(select\s.*from\s|" r"delete\s+from\s|" @@ -78,24 +78,24 @@ def _evaluate_ast(node): wrapper = None statement = "" - if isinstance(node._bandit_parent, ast.BinOp): + if utils.is_instance(node._bandit_parent, "BinOp"): out = utils.concat_string(node, node._bandit_parent) wrapper = out[0]._bandit_parent statement = out[1] elif ( - isinstance(node._bandit_parent, ast.Attribute) + utils.is_instance(node._bandit_parent, "Attribute") and node._bandit_parent.attr == "format" ): statement = node.s # Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str wrapper = node._bandit_parent._bandit_parent._bandit_parent - elif hasattr(ast, "JoinedStr") and isinstance( - node._bandit_parent, ast.JoinedStr + elif utils.check_ast_node("JoinedStr") and utils.is_instance( + node._bandit_parent, "JoinedStr" ): statement = node.s wrapper = node._bandit_parent._bandit_parent - if isinstance(wrapper, ast.Call): # wrapped in "execute" call? + if utils.is_instance(wrapper, "Call"): # wrapped in "execute" call? names = ["execute", "executemany"] name = utils.get_called_name(wrapper) return (name in names, statement) diff --git a/bandit/plugins/jinja2_templates.py b/bandit/plugins/jinja2_templates.py index f0b23e03b..2e05d14a6 100644 --- a/bandit/plugins/jinja2_templates.py +++ b/bandit/plugins/jinja2_templates.py @@ -68,6 +68,7 @@ import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils @test.checks("Call") @@ -79,7 +80,7 @@ def jinja2_autoescape_false(context): func = qualname_list[-1] if "jinja2" in qualname_list and func == "Environment": for node in ast.walk(context.node): - if isinstance(node, ast.keyword): + if utils.is_instance(node, "keyword"): # definite autoescape = False if getattr(node, "arg", None) == "autoescape" and ( getattr(node.value, "id", None) == "False" @@ -105,7 +106,7 @@ def jinja2_autoescape_false(context): return # Check if select_autoescape function is used. elif ( - isinstance(value, ast.Call) + utils.is_instance(value, "Call") and getattr(value.func, "id", None) == "select_autoescape" ): diff --git a/bandit/plugins/try_except_continue.py b/bandit/plugins/try_except_continue.py index c2e3ad493..6d0c5682b 100644 --- a/bandit/plugins/try_except_continue.py +++ b/bandit/plugins/try_except_continue.py @@ -74,11 +74,10 @@ class (or no type). To accommodate this, the test may be configured to ignore CWE information added """ -import ast - import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils def gen_config(name): @@ -99,7 +98,7 @@ def try_except_continue(context, config): ): return - if isinstance(node.body[0], ast.Continue): + if utils.is_instance(node.body[0], "Continue"): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH, diff --git a/bandit/plugins/try_except_pass.py b/bandit/plugins/try_except_pass.py index eda0ef800..091557b0c 100644 --- a/bandit/plugins/try_except_pass.py +++ b/bandit/plugins/try_except_pass.py @@ -72,11 +72,10 @@ class (or no type). To accommodate this, the test may be configured to ignore CWE information added """ -import ast - import bandit from bandit.core import issue from bandit.core import test_properties as test +from bandit.core import utils def gen_config(name): @@ -97,7 +96,7 @@ def try_except_pass(context, config): ): return - if isinstance(node.body[0], ast.Pass): + if utils.is_instance(node.body[0], "Pass"): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH,