From d369741e6cc425a3c1889d98bab7c785f074cea8 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Sat, 5 Dec 2020 17:08:14 -0800 Subject: [PATCH 1/2] Add support for Python 3.9 Add GitHub Action unit testing of Python 3.9 and add to our supported list of Python versions. This patch also fixes some Py39 related issues. Namely: * A hardcoded password such as d["password"] = "blerg" in examples/hardcoded-passwords.py goes undetected. This is due to a change in behavior of the Py3.9 AST. * The README does match the output of bandit -h. Specially the targets is now [targets ...] instead of [targets [targets ...]]. This was introduced with Python fix: https://bugs.python.org/issue38438. As a result, the README no longer contains output of -h and the unit test to make sure they match is gone. Signed-off-by: Eric Brown --- .github/workflows/pythonpackage.yml | 20 +++ README.rst | 166 +------------------ bandit/plugins/general_hardcoded_password.py | 10 ++ examples/hardcoded-passwords.py | 29 ++++ setup.cfg | 1 + tests/functional/test_runtime.py | 12 -- 6 files changed, 64 insertions(+), 174 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index b32071f57..3df7ded44 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -122,3 +122,23 @@ jobs: pip install tox - name: Run tox run: tox -e py38 + + py39: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r test-requirements.txt + pip install tox + - name: Run tox + run: tox -e py39 diff --git a/README.rst b/README.rst index 9d685d9e2..0661ea674 100644 --- a/README.rst +++ b/README.rst @@ -95,168 +95,10 @@ run Bandit with standard input:: cat examples/imports.py | bandit - -Usage:: - - $ bandit -h - usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE] - [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i] - [-f {csv,custom,html,json,screen,txt,xml,yaml}] - [--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d] [-q] - [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE] - [--ini INI_PATH] [--exit-zero] [--version] - [targets [targets ...]] - - Bandit - a Python source code security analyzer - - positional arguments: - targets source file(s) or directory(s) to be tested - - optional arguments: - -h, --help show this help message and exit - -r, --recursive find and process files in subdirectories - -a {file,vuln}, --aggregate {file,vuln} - aggregate output by vulnerability (default) or by - filename - -n CONTEXT_LINES, --number CONTEXT_LINES - maximum number of code lines to output for each issue - -c CONFIG_FILE, --configfile CONFIG_FILE - optional config file to use for selecting plugins and - overriding defaults - -p PROFILE, --profile PROFILE - profile to use (defaults to executing all tests) - -t TESTS, --tests TESTS - comma-separated list of test IDs to run - -s SKIPS, --skip SKIPS - comma-separated list of test IDs to skip - -l, --level report only issues of a given severity level or higher - (-l for LOW, -ll for MEDIUM, -lll for HIGH) - -i, --confidence report only issues of a given confidence level or - higher (-i for LOW, -ii for MEDIUM, -iii for HIGH) - -f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml} - specify output format - --msg-template MSG_TEMPLATE - specify output message template (only usable with - --format custom), see CUSTOM FORMAT section for list - of available values - -o [OUTPUT_FILE], --output [OUTPUT_FILE] - write report to filename - -v, --verbose output extra information like excluded and included - files - -d, --debug turn on debug mode - -q, --quiet, --silent - only show output in the case of an error - --ignore-nosec do not skip lines with # nosec comments - -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS - comma-separated list of paths (glob patterns - supported) to exclude from scan (note that these are - in addition to the excluded paths provided in the - config file) (default: - .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg) - -b BASELINE, --baseline BASELINE - path of a baseline report to compare against (only - JSON-formatted files are accepted) - --ini INI_PATH path to a .bandit file that supplies command line - arguments - --exit-zero exit with 0, even with results found - --version show program's version number and exit - - CUSTOM FORMATTING - ----------------- - - Available tags: - - {abspath}, {relpath}, {line}, {test_id}, - {severity}, {msg}, {confidence}, {range} - - Example usage: - - Default template: - bandit -r examples/ --format custom --msg-template \ - "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}" - - Provides same output as: - bandit -r examples/ --format custom - - Tags can also be formatted in python string.format() style: - bandit -r examples/ --format custom --msg-template \ - "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}" - - See python documentation for more information about formatting style: - https://docs.python.org/3/library/string.html - - The following tests were discovered and loaded: - ----------------------------------------------- - - B101 assert_used - B102 exec_used - B103 set_bad_file_permissions - B104 hardcoded_bind_all_interfaces - B105 hardcoded_password_string - B106 hardcoded_password_funcarg - B107 hardcoded_password_default - B108 hardcoded_tmp_directory - B110 try_except_pass - B112 try_except_continue - B201 flask_debug_true - B301 pickle - B302 marshal - B303 md5 - B304 ciphers - B305 cipher_modes - B306 mktemp_q - B307 eval - B308 mark_safe - B309 httpsconnection - B310 urllib_urlopen - B311 random - B312 telnetlib - B313 xml_bad_cElementTree - B314 xml_bad_ElementTree - B315 xml_bad_expatreader - B316 xml_bad_expatbuilder - B317 xml_bad_sax - B318 xml_bad_minidom - B319 xml_bad_pulldom - B320 xml_bad_etree - B321 ftplib - B322 input - B323 unverified_context - B324 hashlib_new_insecure_functions - B325 tempnam - B401 import_telnetlib - B402 import_ftplib - B403 import_pickle - B404 import_subprocess - B405 import_xml_etree - B406 import_xml_sax - B407 import_xml_expat - B408 import_xml_minidom - B409 import_xml_pulldom - B410 import_lxml - B411 import_xmlrpclib - B412 import_httpoxy - B413 import_pycrypto - B501 request_with_no_cert_validation - B502 ssl_with_bad_version - B503 ssl_with_bad_defaults - B504 ssl_with_no_version - B505 weak_cryptographic_key - B506 yaml_load - B507 ssh_no_host_key_verification - B601 paramiko_calls - B602 subprocess_popen_with_shell_equals_true - B603 subprocess_without_shell_equals_true - B604 any_other_function_with_shell_equals_true - B605 start_process_with_a_shell - B606 start_process_with_no_shell - B607 start_process_with_partial_path - B608 hardcoded_sql_expressions - B609 linux_commands_wildcard_injection - B610 django_extra_used - B611 django_rawsql_used - B701 jinja2_autoescape_false - B702 use_of_mako_templates - B703 django_mark_safe +For more usage information:: + + bandit -h + Baseline -------- diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 2a44c4cc1..7e759d728 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -75,12 +75,22 @@ def hardcoded_password_string(context): """ node = context.node + print(node._bandit_parent) if isinstance(node._bandit_parent, ast.Assign): # looks for "candidate='some_string'" for targ in node._bandit_parent.targets: if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id): return _report(node.s) + elif (isinstance(node._bandit_parent, ast.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): + return _report(assign.value.s) + elif (isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search(node.s)): # looks for "dict[candidate]='some_string'" diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index 8225d4551..d7cbdc553 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -1,29 +1,58 @@ +# Possible hardcoded password: 'Admin' +# Severity: Low Confidence: Medium def someFunction(user, password="Admin"): print("Hi " + user) def someFunction2(password): + # Possible hardcoded password: 'root' + # Severity: Low Confidence: Medium if password == "root": print("OK, logged in") def noMatch(password): + # Possible hardcoded password: '' + # Severity: Low Confidence: Medium if password == '': print("No password!") def NoMatch2(password): + # Possible hardcoded password: 'ajklawejrkl42348swfgkg' + # Severity: Low Confidence: Medium if password == "ajklawejrkl42348swfgkg": print("Nice password!") +# Possible hardcoded password: 'blerg' +# Severity: Low Confidence: Medium def doLogin(password="blerg"): pass def NoMatch3(a, b): pass +# Possible hardcoded password: 'blerg' +# Severity: Low Confidence: Medium doLogin(password="blerg") + +# Possible hardcoded password: 'blerg' +# Severity: Low Confidence: Medium password = "blerg" + +# Possible hardcoded password: 'blerg' +# Severity: Low Confidence: Medium d["password"] = "blerg" +# Possible hardcoded password: 'secret' +# Severity: Low Confidence: Medium EMAIL_PASSWORD = "secret" + +# Possible hardcoded password: 'emails_secret' +# Severity: Low Confidence: Medium email_pwd = 'emails_secret' + +# Possible hardcoded password: 'd6s$f9g!j8mg7hw?n&2' +# Severity: Low Confidence: Medium my_secret_password_for_email = 'd6s$f9g!j8mg7hw?n&2' + +# Possible hardcoded password: '1234' +# Severity: Low Confidence: Medium passphrase='1234' diff --git a/setup.cfg b/setup.cfg index 8f8cfa0c7..52c3bc645 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifier = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Topic :: Security project_urls = Release notes = https://github.com/PyCQA/bandit/releases diff --git a/tests/functional/test_runtime.py b/tests/functional/test_runtime.py index b77a606b9..613ffce76 100644 --- a/tests/functional/test_runtime.py +++ b/tests/functional/test_runtime.py @@ -63,18 +63,6 @@ def test_help_arg(self): self.assertIn("optional arguments:", output) self.assertIn("tests were discovered and loaded:", output) - def test_help_in_readme(self): - replace_list = [' ', '\t', '\n'] - (retcode, output) = self._test_runtime(['bandit', '-h']) - for i in replace_list: - output = output.replace(i, '') - output = output.replace("'", "\'") - with open('README.rst') as f: - readme = f.read() - for i in replace_list: - readme = readme.replace(i, '') - self.assertIn(output, readme) - # test examples (use _test_example() to wrap in config location argument def test_example_nonexistent(self): (retcode, output) = self._test_example( From 58a3e3639de52ff8dae8117d2fd1492ce4e8b637 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Sun, 6 Dec 2020 09:35:27 -0800 Subject: [PATCH 2/2] Update general_hardcoded_password.py --- bandit/plugins/general_hardcoded_password.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 7e759d728..ccccdcf6a 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -75,7 +75,6 @@ def hardcoded_password_string(context): """ node = context.node - print(node._bandit_parent) if isinstance(node._bandit_parent, ast.Assign): # looks for "candidate='some_string'" for targ in node._bandit_parent.targets: