From 8d0b752ab35c4a2b29761391db328970eac6c79d Mon Sep 17 00:00:00 2001 From: ehooo Date: Tue, 8 May 2018 01:46:08 +0200 Subject: [PATCH 01/13] Add detection for Django XSS - Detect mark_safe, SafeText, SafeUnicode, SafeString, SafeBytes - Introspection for detect str assignations for minimize the false positives Related with #294 --- README.rst | 1 + bandit/plugins/django_xss.py | 271 ++++++++++++++++++++++++++++ examples/mark_safe_insecure.py | 159 ++++++++++++++++ examples/mark_safe_secure.py | 75 ++++++++ setup.cfg | 3 + tests/functional/test_functional.py | 24 +++ 6 files changed, 533 insertions(+) create mode 100644 bandit/plugins/django_xss.py create mode 100644 examples/mark_safe_insecure.py create mode 100644 examples/mark_safe_secure.py diff --git a/README.rst b/README.rst index b4d16457e..1942bd094 100644 --- a/README.rst +++ b/README.rst @@ -234,6 +234,7 @@ Usage:: B609 linux_commands_wildcard_injection B701 jinja2_autoescape_false B702 use_of_mako_templates + B703 django_mark_safe Configuration diff --git a/bandit/plugins/django_xss.py b/bandit/plugins/django_xss.py new file mode 100644 index 000000000..233e946a8 --- /dev/null +++ b/bandit/plugins/django_xss.py @@ -0,0 +1,271 @@ +# +# Copyright 2018 Victor Torre +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import ast + +import bandit +from bandit.core import test_properties as test + + +class DeepAssignation(object): + 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): + if node.optional_vars.id == self.var_name.id: + assigned = node + else: + assigned = self.is_assigned_in(node.body) + elif isinstance(node, ast.TryFinally): + assigned = [] + assigned.extend(self.is_assigned_in(node.body)) + assigned.extend(self.is_assigned_in(node.finalbody)) + elif isinstance(node, ast.TryExcept): + 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)) + 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): + 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.id == 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 or call.kwargs: + evaluate = False # TODO(??) get support for this + + if evaluate: + args = list(call.args) + if call.starargs and isinstance(call.starargs, (ast.List, ast.Tuple)): + args.extend(call.starargs.elts) + + 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 + 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.starargs = None + new_call.keywords = None + new_call.kwargs = 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 + elif isinstance(var.right, ast.Dict): + new_call.kwargs = var.right + 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.parent + while not isinstance(parent, (ast.Module, ast.FunctionDef)): + parent = parent.parent + + is_param = False + if isinstance(parent, ast.FunctionDef): + for name in parent.args.args: + if name.id == 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.parent + while not isinstance(parent, (ast.Module, ast.FunctionDef)): + parent = parent.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.parent + while not isinstance(parent, (ast.Module, ast.FunctionDef)): + parent = parent.parent + new_call = transform2call(xss_var) + secure = evaluate_call(new_call, parent) + + if not secure: + return bandit.Issue( + severity=bandit.MEDIUM, + confidence=bandit.HIGH, + text=description + ) + + +@test.checks('Call') +@test.test_id('B703') +def django_mark_safe(context): + """**B703: Potential XSS on mark_safe function** + + .. 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 + + .. versionadded:: 1.4.1 + + """ + 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) diff --git a/examples/mark_safe_insecure.py b/examples/mark_safe_insecure.py new file mode 100644 index 000000000..7304d6b3b --- /dev/null +++ b/examples/mark_safe_insecure.py @@ -0,0 +1,159 @@ +import os +from django.utils import safestring + + +def insecure_function(text, cls=''): + return '

{text}

'.format(text=text, cls=cls) + + +my_insecure_str = insecure_function('insecure', cls='" onload="alert(\'xss\')') +safestring.mark_safe(my_insecure_str) +safestring.SafeText(my_insecure_str) +safestring.SafeUnicode(my_insecure_str) +safestring.SafeString(my_insecure_str) +safestring.SafeBytes(my_insecure_str) + + +def try_insecure(cls='" onload="alert(\'xss\')'): + try: + my_insecure_str = insecure_function('insecure', cls=cls) + except Exception: + my_insecure_str = 'Secure' + safestring.mark_safe(my_insecure_str) + + +def except_insecure(cls='" onload="alert(\'xss\')'): + try: + my_insecure_str = 'Secure' + except Exception: + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe(my_insecure_str) + + +def try_else_insecure(cls='" onload="alert(\'xss\')'): + try: + if 1 == random.randint(0, 1): # nosec + raise Exception + except Exception: + my_insecure_str = 'Secure' + else: + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe(my_insecure_str) + + +def finally_insecure(cls='" onload="alert(\'xss\')'): + try: + if 1 == random.randint(0, 1): # nosec + raise Exception + except Exception: + print "Exception" + else: + print "No Exception" + finally: + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe(my_insecure_str) + + +def format_arg_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('{} {}'.format(my_insecure_str, 'STR')) + + +def format_startarg_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('{}'.format(*[my_insecure_str])) + + +def format_keywords_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('{b}'.format(b=my_insecure_str)) + + +def format_kwargs_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('{b}'.format(**{'b': my_insecure_str})) + + +def percent_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('%s' % my_insecure_str) + + +def percent_list_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('%s %s' % (my_insecure_str, 'b')) + + +def percent_dict_insecure(cls='" onload="alert(\'xss\')'): + my_insecure_str = insecure_function('insecure', cls=cls) + safestring.mark_safe('%(b)s' % {'b': my_insecure_str}) + + +def import_insecure(): + import sre_constants + safestring.mark_safe(sre_constants.ANY) + + +def import_as_insecure(): + import sre_constants.ANY as any_str + safestring.mark_safe(any_str) + + +def from_import_insecure(): + from sre_constants import ANY + safestring.mark_safe(ANY) + + +def from_import_as_insecure(): + from sre_constants import ANY as any_str + safestring.mark_safe(any_str) + + +def with_insecure(path): + with open(path) as f: + safestring.mark_safe(f.read()) + + +def also_with_insecure(path): + with open(path) as f: + safestring.mark_safe(f) + + +def for_insecure(): + my_secure_str = '' + for i in range(random.randint(0, 1)): # nosec + my_secure_str += insecure_function('insecure', cls='" onload="alert(\'xss\')') + safestring.mark_safe(my_secure_str) + + +def while_insecure(): + my_secure_str = '' + while ord(os.urandom(1)) % 2 == 0: + my_secure_str += insecure_function('insecure', cls='" onload="alert(\'xss\')') + safestring.mark_safe(my_secure_str) + + +def some_insecure_case(): + if ord(os.urandom(1)) % 2 == 0: + my_secure_str = insecure_function('insecure', cls='" onload="alert(\'xss\')') + elif ord(os.urandom(1)) % 2 == 0: + my_secure_str = 'Secure' + else: + my_secure_str = 'Secure' + safestring.mark_safe(my_secure_str) + +mystr = 'insecure' + + +def test_insecure_shadow(): # var assigned out of scope + safestring.mark_safe(mystr) + + +def test_insecure(str_arg): + safestring.mark_safe(str_arg) + + +def test_insecure_with_assign(str_arg=None): + if not str_arg: + str_arg = 'could be insecure' + safestring.mark_safe(str_arg) diff --git a/examples/mark_safe_secure.py b/examples/mark_safe_secure.py new file mode 100644 index 000000000..33a81ca99 --- /dev/null +++ b/examples/mark_safe_secure.py @@ -0,0 +1,75 @@ +import os +from django.utils import safestring + +safestring.mark_safe('secure') +safestring.SafeText('secure') +safestring.SafeUnicode('secure') +safestring.SafeString('secure') +safestring.SafeBytes('secure') + +my_secure_str = 'Hello World' +safestring.mark_safe(my_secure_str) + +my_secure_str, _ = ('Hello World', '') +safestring.mark_safe(my_secure_str) + +also_secure_str = my_secure_str +safestring.mark_safe(also_secure_str) + + +def try_secure(): + try: + my_secure_str = 'Secure' + except Exception: + my_secure_str = 'Secure' + else: + my_secure_str = 'Secure' + finally: + my_secure_str = 'Secure' + safestring.mark_safe(my_secure_str) + + +def format_secure(): + safestring.mark_safe('{}'.format('secure')) + my_secure_str = 'secure' + safestring.mark_safe('{}'.format(my_secure_str)) + safestring.mark_safe('{} {}'.format(my_secure_str, 'a')) + safestring.mark_safe('{} {}'.format(*[my_secure_str, 'a'])) + safestring.mark_safe('{b}'.format(b=my_secure_str)) # nosec TODO + safestring.mark_safe('{b}'.format(**{'b': my_secure_str})) # nosec TODO + my_secure_str = '{}'.format(my_secure_str) + safestring.mark_safe(my_secure_str) + + +def percent_secure(): + safestring.mark_safe('%s' % 'secure') + my_secure_str = 'secure' + safestring.mark_safe('%s' % my_secure_str) + safestring.mark_safe('%s %s' % (my_secure_str, 'a')) + safestring.mark_safe('%(b)s' % {'b': my_secure_str}) # nosec TODO + + +def with_secure(path): + with open(path) as f: + safestring.mark_safe('Secure') + + +def loop_secure(): + my_secure_str = '' + + for i in range(ord(os.urandom(1))): + my_secure_str += ' Secure' + safestring.mark_safe(my_secure_str) + while ord(os.urandom(1)) % 2 == 0: + my_secure_str += ' Secure' + safestring.mark_safe(my_secure_str) + + +def all_secure_case(): + if ord(os.urandom(1)) % 2 == 0: + my_secure_str = 'Secure' + elif ord(os.urandom(1)) % 2 == 0: + my_secure_str = 'Secure' + else: + my_secure_str = 'Secure' + safestring.mark_safe(my_secure_str) diff --git a/setup.cfg b/setup.cfg index e4bb7a7a2..fb2a44fd6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -99,6 +99,9 @@ bandit.plugins = # bandit/plugins/mako_templates.py use_of_mako_templates = bandit.plugins.mako_templates:use_of_mako_templates + # bandit/plugins/django_xss.py + django_mark_safe = bandit.plugins.django_xss:django_mark_safe + # bandit/plugins/secret_config_options.py password_config_option_not_marked_secret = bandit.plugins.secret_config_option:password_config_option_not_marked_secret diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 139768839..c8cc4d4e6 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -478,6 +478,30 @@ def test_mako_templating(self): } self.check_example('mako_templating.py', expect) + def test_django_xss_secure(self): + """Test false positives for Django XSS""" + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} + } + self.b_mgr.b_ts = b_test_set.BanditTestSet( + config=self.b_mgr.b_conf, + profile={'exclude': ['B308']} + ) + self.check_example('mark_safe_secure.py', expect) + + def test_django_xss_insecure(self): + """Test for Django XSS via django.utils.safestring""" + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 28, 'HIGH': 0}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 28} + } + self.b_mgr.b_ts = b_test_set.BanditTestSet( + config=self.b_mgr.b_conf, + profile={'exclude': ['B308']} + ) + self.check_example('mark_safe_insecure.py', expect) + def test_xml(self): '''Test xml vulnerabilities.''' expect = { From 05d801b823a9482b9d746d470ec5acbee81cbf40 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Wed, 23 May 2018 21:05:18 -0700 Subject: [PATCH 02/13] Use bandit.readthedocs.io in setup.cfg Bandit should reference bandit.readthedocs.io for the home-page instead of a main PyCQA link. That way those seeing Bandit on PyPI will be properly navigated to the Bandit documentation. Signed-off-by: Eric Brown --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index a27e06dd5..3384990b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ description-file = README.rst author = PyCQA author-email = code-quality@python.org -home-page = http://meta.pycqa.org/en/latest/ +home-page = https://bandit.readthedocs.io/en/latest/ classifier = Environment :: Console Intended Audience :: Information Technology From b32917e774733c096ca6c279ae0be3a8c06f2d0f Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Tue, 29 May 2018 12:51:12 -0700 Subject: [PATCH 03/13] Add missing documentation link for B703 PR #295 added a new plugin to check for django XSS, but forgot a link to the plugin documentation. This patch adds just that. --- doc/source/plugins/b703_django_mark_safe.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/source/plugins/b703_django_mark_safe.rst diff --git a/doc/source/plugins/b703_django_mark_safe.rst b/doc/source/plugins/b703_django_mark_safe.rst new file mode 100644 index 000000000..8be951379 --- /dev/null +++ b/doc/source/plugins/b703_django_mark_safe.rst @@ -0,0 +1,5 @@ +---------------------- +B703: django_mark_safe +---------------------- + +.. automodule:: bandit.plugins.django_mark_safe From 5b430f36f7f8ff771469acf99655db6ae8eaec49 Mon Sep 17 00:00:00 2001 From: nickthetait Date: Tue, 22 May 2018 11:56:25 -0600 Subject: [PATCH 04/13] Remove OpenStack-specific plugins Fixes: #296 Signed-off-by: nickthetait --- bandit/plugins/exec_as_root.py | 94 -------------- bandit/plugins/secret_config_option.py | 115 ------------------ ...ssword_config_option_not_marked_secret.rst | 5 - ...1_execute_with_run_as_root_equals_true.rst | 5 - setup.cfg | 6 - tests/functional/test_functional.py | 16 --- 6 files changed, 241 deletions(-) delete mode 100644 bandit/plugins/exec_as_root.py delete mode 100644 bandit/plugins/secret_config_option.py delete mode 100644 doc/source/plugins/b109_password_config_option_not_marked_secret.rst delete mode 100644 doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst diff --git a/bandit/plugins/exec_as_root.py b/bandit/plugins/exec_as_root.py deleted file mode 100644 index 558ed4833..000000000 --- a/bandit/plugins/exec_as_root.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -r""" -================================================== -B111: Test for the use of rootwrap running as root -================================================== - -Running commands as root dramatically increase their potential risk. Running -commands with restricted user privileges provides defense in depth against -command injection attacks, or developer and configuration error. This plugin -test checks for specific methods being called with a keyword parameter -`run_as_root` set to True, a common OpenStack idiom. - - -**Config Options:** - -This test plugin takes a similarly named configuration block, -`execute_with_run_as_root_equals_true`, providing a list, `function_names`, of -function names. A call to any of these named functions will be checked for a -`run_as_root` keyword parameter, and if True, will report a Low severity -issue. - -.. code-block:: yaml - - execute_with_run_as_root_equals_true: - function_names: - - ceilometer.utils.execute - - cinder.utils.execute - - neutron.agent.linux.utils.execute - - nova.utils.execute - - nova.utils.trycmd - - -:Example: - -.. code-block:: none - - >> Issue: Execute with run_as_root=True identified, possible security - issue. - Severity: Low Confidence: Medium - Location: ./examples/exec-as-root.py:26 - 25 nova_utils.trycmd('gcc --version') - 26 nova_utils.trycmd('gcc --version', run_as_root=True) - 27 - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_rootwrap-recommendations-and-plans.html # noqa - - https://security.openstack.org/guidelines/dg_use-oslo-rootwrap-securely.html - -.. versionadded:: 0.10.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'execute_with_run_as_root_equals_true': - return {'function_names': - ['ceilometer.utils.execute', - 'cinder.utils.execute', - 'neutron.agent.linux.utils.execute', - 'nova.utils.execute', - 'nova.utils.trycmd']} - - -@test.takes_config -@test.checks('Call') -@test.test_id('B111') -def execute_with_run_as_root_equals_true(context, config): - - if (context.call_function_name_qual in config['function_names']): - if context.check_call_arg_value('run_as_root', 'True'): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.MEDIUM, - text="Execute with run_as_root=True identified, possible " - "security issue.", - lineno=context.get_lineno_for_call_arg('run_as_root'), - ) diff --git a/bandit/plugins/secret_config_option.py b/bandit/plugins/secret_config_option.py deleted file mode 100644 index 97555b5ca..000000000 --- a/bandit/plugins/secret_config_option.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -r""" -=============================================================== -B109: Test for a password based config option not marked secret -=============================================================== - -Passwords are sensitive and must be protected appropriately. In OpenStack -Oslo there is an option to mark options "secret" which will ensure that they -are not logged. This plugin detects usages of oslo configuration functions -that appear to deal with strings ending in 'password' and flag usages where -they have not been marked secret. - -If such a value is found a MEDIUM severity error is generated. If 'False' or -'None' are explicitly set, Bandit will return a MEDIUM confidence issue. If -Bandit can't determine the value of secret it will return a LOW confidence -issue. - - -**Config Options:** - -.. code-block:: yaml - - password_config_option_not_marked_secret: - function_names: - - oslo.config.cfg.StrOpt - - oslo_config.cfg.StrOpt - -:Example: - -.. code-block:: none - - >> Issue: [password_config_option_not_marked_secret] oslo config option - possibly not marked secret=True identified. - Severity: Medium Confidence: Low - Location: examples/secret-config-option.py:12 - 11 help="User's password"), - 12 cfg.StrOpt('nova_password', - 13 secret=secret, - 14 help="Nova user password"), - 15 ] - - >> Issue: [password_config_option_not_marked_secret] oslo config option not - marked secret=True identified, security issue. - Severity: Medium Confidence: Medium - Location: examples/secret-config-option.py:21 - 20 help="LDAP ubind ser name"), - 21 cfg.StrOpt('ldap_password', - 22 help="LDAP bind user password"), - 23 cfg.StrOpt('ldap_password_attribute', - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_protect-sensitive-data-in-files.html # noqa - -.. versionadded:: 0.10.0 - -""" - -import bandit -from bandit.core import constants -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'password_config_option_not_marked_secret': - return {'function_names': - ['oslo.config.cfg.StrOpt', - 'oslo_config.cfg.StrOpt']} - - -@test.takes_config -@test.checks('Call') -@test.test_id('B109') -def password_config_option_not_marked_secret(context, config): - - if(context.call_function_name_qual in config['function_names'] and - context.get_call_arg_at_position(0) is not None and - context.get_call_arg_at_position(0).endswith('password')): - - # Checks whether secret=False or secret is not set (None). - # Returns True if argument found, and matches supplied values - # and None if argument not found at all. - if context.check_call_arg_value('secret', - constants.FALSE_VALUES) in [ - True, None]: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="oslo config option not marked secret=True " - "identified, security issue.", - lineno=context.get_lineno_for_call_arg('secret'), - ) - # Checks whether secret is not True, for example when its set to a - # variable, secret=secret. - elif not context.check_call_arg_value('secret', 'True'): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.LOW, - text="oslo config option possibly not marked secret=True " - "identified.", - lineno=context.get_lineno_for_call_arg('secret'), - ) diff --git a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst deleted file mode 100644 index ac84217d8..000000000 --- a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst +++ /dev/null @@ -1,5 +0,0 @@ ----------------------------------------------- -B109: password_config_option_not_marked_secret ----------------------------------------------- - -.. automodule:: bandit.plugins.secret_config_option diff --git a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst deleted file mode 100644 index 4093c36f4..000000000 --- a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------------------- -B111: execute_with_run_as_root_equals_true ------------------------------------------- - -.. automodule:: bandit.plugins.exec_as_root diff --git a/setup.cfg b/setup.cfg index 172164164..5bd6b43dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,9 +49,6 @@ bandit.plugins = # bandit/plugins/crypto_request_no_cert_validation.py request_with_no_cert_validation = bandit.plugins.crypto_request_no_cert_validation:request_with_no_cert_validation - # bandit/plugins/exec_as_root.py - execute_with_run_as_root_equals_true = bandit.plugins.exec_as_root:execute_with_run_as_root_equals_true - # bandit/plugins/exec.py exec_used = bandit.plugins.exec:exec_used @@ -107,9 +104,6 @@ bandit.plugins = # bandit/plugins/django_xss.py django_mark_safe = bandit.plugins.django_xss:django_mark_safe - # bandit/plugins/secret_config_options.py - password_config_option_not_marked_secret = bandit.plugins.secret_config_option:password_config_option_not_marked_secret - # bandit/plugins/try_except_continue.py try_except_continue = bandit.plugins.try_except_continue:try_except_continue diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 6f29a5c9f..f8d7176cd 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -176,14 +176,6 @@ def test_exec(self): } self.check_example(filename, expect) - def test_exec_as_root(self): - '''Test for the `run_as_root=True` keyword argument.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 0} - } - self.check_example('exec-as-root.py', expect) - def test_hardcoded_passwords(self): '''Test for hard-coded passwords.''' expect = { @@ -480,14 +472,6 @@ def test_jinja2_templating(self): } self.check_example('jinja2_templating.py', expect) - def test_secret_config_option(self): - '''Test for `secret=True` in Oslo's config.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 0} - } - self.check_example('secret-config-option.py', expect) - def test_mako_templating(self): '''Test Mako templates for XSS.''' expect = { From 45df4fd2d7e29ab73e7707b2b7d78fa3fcb18921 Mon Sep 17 00:00:00 2001 From: nickthetait Date: Fri, 1 Jun 2018 15:09:12 -0600 Subject: [PATCH 05/13] Leave a message explaining that these plugins have been deprecated --- ..._password_config_option_not_marked_secret.rst | 16 ++++++++++++++++ ...b111_execute_with_run_as_root_equals_true.rst | 11 +++++++++++ 2 files changed, 27 insertions(+) create mode 100644 doc/source/plugins/b109_password_config_option_not_marked_secret.rst create mode 100644 doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst diff --git a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst new file mode 100644 index 000000000..aa2c5aa5a --- /dev/null +++ b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst @@ -0,0 +1,16 @@ +--------------------------------------------------------------- +B109: Test for a password based config option not marked secret +--------------------------------------------------------------- + +This plugin has been deprecated. + +Passwords are sensitive and must be protected appropriately. In OpenStack +Oslo there is an option to mark options "secret" which will ensure that they +are not logged. This plugin detects usages of oslo configuration functions +that appear to deal with strings ending in 'password' and flag usages where +they have not been marked secret. + +If such a value is found a MEDIUM severity error is generated. If 'False' or +'None' are explicitly set, Bandit will return a MEDIUM confidence issue. If +Bandit can't determine the value of secret it will return a LOW confidence +issue. diff --git a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst new file mode 100644 index 000000000..4a88760c2 --- /dev/null +++ b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst @@ -0,0 +1,11 @@ +-------------------------------------------------- +B111: Test for the use of rootwrap running as root +-------------------------------------------------- + +This plugin has been deprecated. + +Running commands as root dramatically increase their potential risk. Running +commands with restricted user privileges provides defense in depth against +command injection attacks, or developer and configuration error. This plugin +test checks for specific methods being called with a keyword parameter +`run_as_root` set to True, a common OpenStack idiom. From d93eed549242013ca10f6fc990dd4234b69958f7 Mon Sep 17 00:00:00 2001 From: nickthetait Date: Thu, 7 Jun 2018 09:28:46 -0600 Subject: [PATCH 06/13] Fix wording (deprecated -> removed) Signed-off-by: nickthetait --- .../plugins/b109_password_config_option_not_marked_secret.rst | 2 +- .../plugins/b111_execute_with_run_as_root_equals_true.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst index aa2c5aa5a..0d2e5857b 100644 --- a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst +++ b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst @@ -2,7 +2,7 @@ B109: Test for a password based config option not marked secret --------------------------------------------------------------- -This plugin has been deprecated. +This plugin has been removed. Passwords are sensitive and must be protected appropriately. In OpenStack Oslo there is an option to mark options "secret" which will ensure that they diff --git a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst index 4a88760c2..f39e7c14b 100644 --- a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst +++ b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst @@ -2,7 +2,7 @@ B111: Test for the use of rootwrap running as root -------------------------------------------------- -This plugin has been deprecated. +This plugin has been removed. Running commands as root dramatically increase their potential risk. Running commands with restricted user privileges provides defense in depth against From 4e565e5db1ef2c69e2e0661da0088aeb3fffaacb Mon Sep 17 00:00:00 2001 From: ehooo Date: Tue, 8 May 2018 03:08:31 +0200 Subject: [PATCH 07/13] Example for shell kwarg Related with #157 --- examples/subprocess_shell.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/examples/subprocess_shell.py b/examples/subprocess_shell.py index d0bd3cc91..3e7aafe75 100644 --- a/examples/subprocess_shell.py +++ b/examples/subprocess_shell.py @@ -5,6 +5,9 @@ def Popen(*args, **kwargs): print('hi') + def __len__(self): + return 0 + pop('/bin/gcc --version', shell=True) Popen('/bin/gcc --version', shell=True) @@ -31,3 +34,17 @@ def Popen(*args, **kwargs): subprocess.Popen(command, shell=True) subprocess.Popen('/bin/ls && cat /etc/passwd', shell=True) + +# Issue #157 +command = 'pwd' +subprocess.call(command, shell='True') +subprocess.call(command, shell=1) + +# HOW TO manage it ?? +# subprocess.call(command, shell=Popen()) + +subprocess.call(command, shell=False) +subprocess.call(command, shell=0) +subprocess.call(command, shell=[]) +subprocess.call(command, shell={}) +subprocess.call(command, shell=None) From f8e5662e94bc35a8b37dd9efcb8ffc36f065f451 Mon Sep 17 00:00:00 2001 From: ehooo Date: Tue, 8 May 2018 23:39:34 +0200 Subject: [PATCH 08/13] Improve add shell=True detecction - More example for shell kwarg - Related with #157 --- bandit/plugins/injection_shell.py | 25 ++++++++++++++++++++++--- examples/subprocess_shell.py | 8 ++++++-- tests/functional/test_functional.py | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/bandit/plugins/injection_shell.py b/bandit/plugins/injection_shell.py index 52f06f973..9641a7011 100644 --- a/bandit/plugins/injection_shell.py +++ b/bandit/plugins/injection_shell.py @@ -85,6 +85,25 @@ def gen_config(name): } +def has_shell(context): + keywords = context.node.keywords + if 'shell' in context.call_keywords: + for key in keywords: + if key.arg == 'shell': + val = key.value + if isinstance(val, ast.Num): + return bool(val.n) + if isinstance(val, ast.List): + return bool(val.elts) + if isinstance(val, ast.Dict): + return bool(val.keys) + if isinstance(val, ast.Name): + if val.id in ['False', 'None']: + return False + return True + return False + + @test.takes_config('shell_injection') @test.checks('Call') @test.test_id('B602') @@ -180,7 +199,7 @@ def subprocess_popen_with_shell_equals_true(context, config): .. versionadded:: 0.9.0 """ if config and context.call_function_name_qual in config['subprocess']: - if context.check_call_arg_value('shell', 'True'): + if has_shell(context): if len(context.call_args) > 0: sev = _evaluate_shell_call(context) if sev == bandit.LOW: @@ -271,7 +290,7 @@ def subprocess_without_shell_equals_true(context, config): .. versionadded:: 0.9.0 """ if config and context.call_function_name_qual in config['subprocess']: - if not context.check_call_arg_value('shell', 'True'): + if not has_shell(context): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH, @@ -349,7 +368,7 @@ def any_other_function_with_shell_equals_true(context, config): .. versionadded:: 0.9.0 """ if config and context.call_function_name_qual not in config['subprocess']: - if context.check_call_arg_value('shell', 'True'): + if has_shell(context): return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.LOW, diff --git a/examples/subprocess_shell.py b/examples/subprocess_shell.py index 3e7aafe75..aaa76f0c6 100644 --- a/examples/subprocess_shell.py +++ b/examples/subprocess_shell.py @@ -38,10 +38,14 @@ def __len__(self): # Issue #157 command = 'pwd' subprocess.call(command, shell='True') +subprocess.call(command, shell='False') +subprocess.call(command, shell='None') subprocess.call(command, shell=1) -# HOW TO manage it ?? -# subprocess.call(command, shell=Popen()) +subprocess.call(command, shell=Popen()) +subprocess.call(command, shell=[True]) +subprocess.call(command, shell={'IS': 'True'}) +subprocess.call(command, shell=command) subprocess.call(command, shell=False) subprocess.call(command, shell=0) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index f8d7176cd..bcd89d70c 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -409,8 +409,8 @@ def test_ssl_insecure_version(self): def test_subprocess_shell(self): '''Test for `subprocess.Popen` with `shell=True`.''' expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 14, 'MEDIUM': 1, 'HIGH': 3}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 17} + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 19, 'MEDIUM': 1, 'HIGH': 11}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 30} } self.check_example('subprocess_shell.py', expect) From baac2e2140038c70c1375eb60e5f0a705af01bbe Mon Sep 17 00:00:00 2001 From: ehooo Date: Thu, 10 May 2018 22:55:40 +0200 Subject: [PATCH 09/13] Remove issue comment --- examples/subprocess_shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/subprocess_shell.py b/examples/subprocess_shell.py index aaa76f0c6..8764af197 100644 --- a/examples/subprocess_shell.py +++ b/examples/subprocess_shell.py @@ -35,7 +35,6 @@ def __len__(self): subprocess.Popen('/bin/ls && cat /etc/passwd', shell=True) -# Issue #157 command = 'pwd' subprocess.call(command, shell='True') subprocess.call(command, shell='False') From d3f8164ecbb799eaa2576f8a0d144662f3e725a4 Mon Sep 17 00:00:00 2001 From: ehooo Date: Thu, 10 May 2018 23:30:31 +0200 Subject: [PATCH 10/13] Fast fix for #286 - Fix pep8 in example - Check importation --- bandit/plugins/yaml_load.py | 3 ++- examples/yaml_lib_load.py | 7 +++++++ examples/yaml_load.py | 4 +++- tests/functional/test_functional.py | 6 ++++++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 examples/yaml_lib_load.py diff --git a/bandit/plugins/yaml_load.py b/bandit/plugins/yaml_load.py index 01a93e92b..9d833c7fd 100644 --- a/bandit/plugins/yaml_load.py +++ b/bandit/plugins/yaml_load.py @@ -55,7 +55,8 @@ @test.test_id('B506') @test.checks('Call') def yaml_load(context): - if isinstance(context.call_function_name_qual, str): + if context.is_module_imported_exact('yaml') and \ + isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if 'yaml' in qualname_list and func == 'load': diff --git a/examples/yaml_lib_load.py b/examples/yaml_lib_load.py new file mode 100644 index 000000000..e1087e9da --- /dev/null +++ b/examples/yaml_lib_load.py @@ -0,0 +1,7 @@ +from ruamel.yaml import YAML + + +def not_a_vul(content): + yaml = YAML(typ='safe') + c = yaml.load(content) + diff --git a/examples/yaml_load.py b/examples/yaml_load.py index c14be339c..5626fa807 100644 --- a/examples/yaml_load.py +++ b/examples/yaml_load.py @@ -1,12 +1,14 @@ import json import yaml + def test_yaml_load(): - ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3}) + ystr = yaml.dump({'a': 1, 'b': 2, 'c': 3}) y = yaml.load(ystr) yaml.dump(y) y = yaml.load(ystr, Loader=yaml.SafeLoader) + def test_json_load(): # no issue should be found j = json.load("{}") diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index bcd89d70c..3a45648cf 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -464,6 +464,12 @@ def test_yaml(self): } self.check_example('yaml_load.py', expect) + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} + } + self.check_example('yaml_lib_load.py', expect) + def test_jinja2_templating(self): '''Test jinja templating for potential XSS bugs.''' expect = { From 82f877bc478f068c6f91a6b739546828dadef4da Mon Sep 17 00:00:00 2001 From: ehooo Date: Sat, 12 May 2018 00:30:22 +0200 Subject: [PATCH 11/13] Fix code review --- bandit/plugins/yaml_load.py | 7 ++++--- examples/yaml_lib_load.py | 7 ------- tests/functional/test_functional.py | 6 ------ 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 examples/yaml_lib_load.py diff --git a/bandit/plugins/yaml_load.py b/bandit/plugins/yaml_load.py index 9d833c7fd..da2353bfc 100644 --- a/bandit/plugins/yaml_load.py +++ b/bandit/plugins/yaml_load.py @@ -55,9 +55,10 @@ @test.test_id('B506') @test.checks('Call') def yaml_load(context): - if context.is_module_imported_exact('yaml') and \ - isinstance(context.call_function_name_qual, str): - qualname_list = context.call_function_name_qual.split('.') + imported = context.is_module_imported_exact('yaml') + qualname = context.call_function_name_qual + if imported and isinstance(qualname, str): + qualname_list = qualname.split('.') func = qualname_list[-1] if 'yaml' in qualname_list and func == 'load': if not context.check_call_arg_value('Loader', 'SafeLoader'): diff --git a/examples/yaml_lib_load.py b/examples/yaml_lib_load.py deleted file mode 100644 index e1087e9da..000000000 --- a/examples/yaml_lib_load.py +++ /dev/null @@ -1,7 +0,0 @@ -from ruamel.yaml import YAML - - -def not_a_vul(content): - yaml = YAML(typ='safe') - c = yaml.load(content) - diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 3a45648cf..bcd89d70c 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -464,12 +464,6 @@ def test_yaml(self): } self.check_example('yaml_load.py', expect) - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} - } - self.check_example('yaml_lib_load.py', expect) - def test_jinja2_templating(self): '''Test jinja templating for potential XSS bugs.''' expect = { From e2b777b4e2d85f1370315005486c982e0a1a05aa Mon Sep 17 00:00:00 2001 From: ehooo Date: Thu, 17 May 2018 08:31:35 +0200 Subject: [PATCH 12/13] Add doc and version --- bandit/plugins/django_sql_injection.py | 5 +++-- doc/source/plugins/b610_django_extra_used.rst | 5 +++++ doc/source/plugins/b611_django_rawsql_used.rst | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 doc/source/plugins/b610_django_extra_used.rst create mode 100644 doc/source/plugins/b611_django_rawsql_used.rst diff --git a/bandit/plugins/django_sql_injection.py b/bandit/plugins/django_sql_injection.py index 81d8b50e7..664e8aab6 100644 --- a/bandit/plugins/django_sql_injection.py +++ b/bandit/plugins/django_sql_injection.py @@ -16,6 +16,7 @@ import ast + import bandit from bandit.core import test_properties as test @@ -38,7 +39,7 @@ def django_extra_used(context): - https://docs.djangoproject.com/en/dev/topics/ security/#sql-injection-protection - .. versionadded:: X.X.X + .. versionadded:: 1.4.1 """ description = "Use of extra potential SQL attack vector." @@ -101,7 +102,7 @@ def django_rawsql_used(context): - https://docs.djangoproject.com/en/dev/topics/ security/#sql-injection-protection - .. versionadded:: X.X.X + .. versionadded:: 1.4.1 """ description = "Use of RawSQL potential SQL attack vector." diff --git a/doc/source/plugins/b610_django_extra_used.rst b/doc/source/plugins/b610_django_extra_used.rst new file mode 100644 index 000000000..3f90488b4 --- /dev/null +++ b/doc/source/plugins/b610_django_extra_used.rst @@ -0,0 +1,5 @@ +----------------------- +B610: django_extra_used +----------------------- + +.. automodule:: bandit.plugins.django_injection_sql diff --git a/doc/source/plugins/b611_django_rawsql_used.rst b/doc/source/plugins/b611_django_rawsql_used.rst new file mode 100644 index 000000000..e37fedfce --- /dev/null +++ b/doc/source/plugins/b611_django_rawsql_used.rst @@ -0,0 +1,5 @@ +------------------------ +B610: django_rawsql_used +------------------------ + +.. automodule:: bandit.plugins.django_injection_sql From 386eb19952f4e41fb8f0cfa3786c1b2fe2202f3d Mon Sep 17 00:00:00 2001 From: ehooo Date: Fri, 18 May 2018 00:24:15 +0200 Subject: [PATCH 13/13] Fix doc #310 --- doc/source/plugins/b610_django_extra_used.rst | 5 ++++- doc/source/plugins/b611_django_rawsql_used.rst | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/source/plugins/b610_django_extra_used.rst b/doc/source/plugins/b610_django_extra_used.rst index 3f90488b4..870e79948 100644 --- a/doc/source/plugins/b610_django_extra_used.rst +++ b/doc/source/plugins/b610_django_extra_used.rst @@ -2,4 +2,7 @@ B610: django_extra_used ----------------------- -.. automodule:: bandit.plugins.django_injection_sql +.. currentmodule:: bandit.plugins.django_injection_sql + +.. autofunction:: django_extra_used + :noindex: diff --git a/doc/source/plugins/b611_django_rawsql_used.rst b/doc/source/plugins/b611_django_rawsql_used.rst index e37fedfce..d83a17d61 100644 --- a/doc/source/plugins/b611_django_rawsql_used.rst +++ b/doc/source/plugins/b611_django_rawsql_used.rst @@ -2,4 +2,7 @@ B610: django_rawsql_used ------------------------ -.. automodule:: bandit.plugins.django_injection_sql +.. currentmodule:: bandit.plugins.django_injection_sql + +.. autofunction:: django_rawsql_used + :noindex: