From 8ecb8d1593d3d010fe321859c9f41dc659756e3c Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Tue, 16 Nov 2021 18:23:17 +0100 Subject: [PATCH] New check: B113: TrojanSource - Bidirectional control characters --- bandit/core/issue.py | 2 +- bandit/core/node_visitor.py | 8 +++++ bandit/core/test_properties.py | 6 +++- bandit/core/tester.py | 5 +-- bandit/plugins/trojansource.py | 53 +++++++++++++++++++++++++++++ doc/source/plugins/trojansource.rst | 5 +++ examples/trojansource.py | 5 +++ setup.cfg | 3 ++ tests/functional/test_functional.py | 7 ++++ 9 files changed, 90 insertions(+), 4 deletions(-) create mode 100755 bandit/plugins/trojansource.py create mode 100644 doc/source/plugins/trojansource.rst create mode 100644 examples/trojansource.py diff --git a/bandit/core/issue.py b/bandit/core/issue.py index 98b1d0bb2..603c3c091 100644 --- a/bandit/core/issue.py +++ b/bandit/core/issue.py @@ -14,7 +14,7 @@ class Issue(object): def __init__(self, severity, confidence=constants.CONFIDENCE_DEFAULT, - text="", ident=None, lineno=None, test_id="", col_offset=0): + text="", ident=None, lineno=None, test_id="", col_offset=-1): self.severity = severity self.confidence = confidence if isinstance(text, bytes): diff --git a/bandit/core/node_visitor.py b/bandit/core/node_visitor.py index eba93e0d7..23b6cd58c 100644 --- a/bandit/core/node_visitor.py +++ b/bandit/core/node_visitor.py @@ -288,4 +288,12 @@ def process(self, data): ''' f_ast = ast.parse(data) self.generic_visit(f_ast) + # Run tests that do not require access to the AST but only to the whole file source: + self.context = { + 'filename': self.fname, + 'lineno': 0, + 'linerange': [0, 1], + 'col_offset': 0, + } + self.update_scores(self.tester.run_tests(self.context, 'File')) return self.scores diff --git a/bandit/core/test_properties.py b/bandit/core/test_properties.py index be328a38f..98bd21d0a 100644 --- a/bandit/core/test_properties.py +++ b/bandit/core/test_properties.py @@ -16,7 +16,11 @@ def checks(*args): def wrapper(func): if not hasattr(func, "_checks"): func._checks = [] - func._checks.extend(utils.check_ast_node(a) for a in args) + for arg in args: + if arg == "File": + func._checks.append("File") + else: + func._checks.append(utils.check_ast_node(arg)) LOG.debug('checks() decorator executed') LOG.debug(' func._checks: %s', func._checks) diff --git a/bandit/core/tester.py b/bandit/core/tester.py index fc46655a1..d8b8bfc9a 100644 --- a/bandit/core/tester.py +++ b/bandit/core/tester.py @@ -44,7 +44,7 @@ def run_tests(self, raw_context, checktype): tests = self.testset.get_tests(checktype) for test in tests: name = test.__name__ - # execute test with the an instance of the context class + # execute test with an instance of the context class temp_context = copy.copy(raw_context) context = b_context.Context(temp_context) try: @@ -66,7 +66,8 @@ def run_tests(self, raw_context, checktype): if result.lineno is None: result.lineno = temp_context['lineno'] result.linerange = temp_context['linerange'] - result.col_offset = temp_context['col_offset'] + if result.col_offset == -1: + result.col_offset = temp_context['col_offset'] result.test = name if result.test_id == "": result.test_id = test._test_id diff --git a/bandit/plugins/trojansource.py b/bandit/plugins/trojansource.py new file mode 100755 index 000000000..5749c1f5f --- /dev/null +++ b/bandit/plugins/trojansource.py @@ -0,0 +1,53 @@ +# -*- coding:utf-8 -*- +# + +r""" +===================================================== +B113: TrojanSource - Bidirectional control characters +===================================================== + +This plugin checks for the presence of unicode bidirectional control characters +in Python source files. Those characters can be embedded in comments and strings +to reorder source code characters in a way that changes its logic. + +:Example: + +.. code-block:: none + + >> Issue: [B113:trojansource] A Python source file seems to contain bidirectional control characters ('\u202e'). + Severity: High Confidence: Medium + Location: examples/trojansource.py:0:0 + +.. seealso:: + + .. [1] https://trojansource.codes/ + .. [2] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574 + +.. versionadded:: 1.7.1 + +""" # noqa: E501 + +import bandit +from bandit.core import test_properties as test + + +BIDI_CHARACTERS = ('\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069') + + +@test.test_id('B113') +@test.checks('File') +def trojansource(context): + with open(context.filename, encoding='utf8') as src_file: + for lineno, line in enumerate(src_file.readlines(), start=1): + for char in BIDI_CHARACTERS: + try: + col_offset = line.index(char) + 1 + except ValueError: + continue + return bandit.Issue( + severity=bandit.HIGH, + confidence=bandit.MEDIUM, + text="A Python source file seems to contain bidirectional control characters (%r)." % char, + lineno=lineno, + col_offset=col_offset, + ) diff --git a/doc/source/plugins/trojansource.rst b/doc/source/plugins/trojansource.rst new file mode 100644 index 000000000..f681d202d --- /dev/null +++ b/doc/source/plugins/trojansource.rst @@ -0,0 +1,5 @@ +------------------ +B113: trojansource +------------------ + +.. automodule:: bandit.plugins.trojansource diff --git a/examples/trojansource.py b/examples/trojansource.py new file mode 100644 index 000000000..40c605579 --- /dev/null +++ b/examples/trojansource.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# cf. https://trojansource.codes/ & https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574 +access_level = "user" +if access_level != 'none‮⁦': # Check if admin ⁩⁦' and access_level != 'user + print("You are an admin.\n") diff --git a/setup.cfg b/setup.cfg index 342f46446..849fec0eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -130,6 +130,9 @@ bandit.plugins = # bandit/plugins/ssh_no_host_key_verification.py ssh_no_host_key_verification = bandit.plugins.ssh_no_host_key_verification:ssh_no_host_key_verification + # bandit/plugins/trojansource.py + trojansource = bandit.plugins.trojansource:trojansource + [build_sphinx] all_files = 1 build-dir = doc/build diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 173d4d67b..ab022b106 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -790,3 +790,10 @@ def test_no_blacklist_pycryptodome(self): 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} } self.check_example('pycryptodome.py', expect) + + def test_trojansource(self): + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0} + } + self.check_example('trojansource.py', expect)