Skip to content

Commit

Permalink
New check: B113: TrojanSource - Bidirectional control characters
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Nov 17, 2021
1 parent e0a12a9 commit 8ecb8d1
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 4 deletions.
2 changes: 1 addition & 1 deletion bandit/core/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 8 additions & 0 deletions bandit/core/node_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion bandit/core/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions bandit/core/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
53 changes: 53 additions & 0 deletions bandit/plugins/trojansource.py
Original file line number Diff line number Diff line change
@@ -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,
)
5 changes: 5 additions & 0 deletions doc/source/plugins/trojansource.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
------------------
B113: trojansource
------------------

.. automodule:: bandit.plugins.trojansource
5 changes: 5 additions & 0 deletions examples/trojansource.py
Original file line number Diff line number Diff line change
@@ -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")
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions tests/functional/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 8ecb8d1

Please sign in to comment.