diff --git a/bandit/core/blacklisting.py b/bandit/core/blacklisting.py index 6e015176e..a79b7274f 100644 --- a/bandit/core/blacklisting.py +++ b/bandit/core/blacklisting.py @@ -5,6 +5,7 @@ import ast import fnmatch +from bandit.core import cwemap from bandit.core import issue @@ -13,6 +14,7 @@ def report_issue(check, name): severity=check.get("level", "MEDIUM"), confidence="HIGH", text=check["message"].replace("{name}", name), + cwe=cwemap.CWEMAP[check.get("id", "LEGACY")], ident=name, test_id=check.get("id", "LEGACY"), ) diff --git a/bandit/core/cwemap.py b/bandit/core/cwemap.py new file mode 100644 index 000000000..77144c9bb --- /dev/null +++ b/bandit/core/cwemap.py @@ -0,0 +1,79 @@ +# +# SPDX-License-Identifier: Apache-2.0 +from bandit.core import issue + +CWEMAP = { + "B000": issue.Cwe.NOTSET, + "LEGACY": issue.Cwe.NOTSET, + # Plugins + "B101": issue.Cwe.IMPROPER_CHECK_OF_EXCEPT_COND, + "B102": issue.Cwe.OS_COMMAND_INJECTION, + "B103": issue.Cwe.INCORRECT_PERMISSION_ASSIGNMENT, + "B104": issue.Cwe.MULTIPLE_BINDS, + "B105": issue.Cwe.HARD_CODED_PASSWORD, + "B108": issue.Cwe.INSECURE_TEMP_FILE, + "B110": issue.Cwe.IMPROPER_CHECK_OF_EXCEPT_COND, + "B112": issue.Cwe.IMPROPER_CHECK_OF_EXCEPT_COND, + "B201": issue.Cwe.CODE_INJECTION, + "B324": issue.Cwe.BROKEN_CRYPTO, + "B501": issue.Cwe.IMPROPER_CERT_VALIDATION, + "B502": issue.Cwe.BROKEN_CRYPTO, + "B503": issue.Cwe.BROKEN_CRYPTO, + "B504": issue.Cwe.BROKEN_CRYPTO, + "B505": issue.Cwe.INADEQUATE_ENCRYPTION_STRENGTH, + "B506": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B507": issue.Cwe.IMPROPER_CERT_VALIDATION, + "B601": issue.Cwe.OS_COMMAND_INJECTION, + "B602": issue.Cwe.OS_COMMAND_INJECTION, + "B603": issue.Cwe.OS_COMMAND_INJECTION, + "B604": issue.Cwe.OS_COMMAND_INJECTION, + "B605": issue.Cwe.OS_COMMAND_INJECTION, + "B606": issue.Cwe.OS_COMMAND_INJECTION, + "B607": issue.Cwe.OS_COMMAND_INJECTION, + "B608": issue.Cwe.SQL_INJECTION, + "B609": issue.Cwe.IMPROPER_WILDCARD_NEUTRALIZATION, + "B611": issue.Cwe.SQL_INJECTION, + "B701": issue.Cwe.CODE_INJECTION, + "B702": issue.Cwe.BASIC_XSS, + "B703": issue.Cwe.BASIC_XSS, + # Calls + "B301": issue.Cwe.DESERIALIZATION_OF_UNTRUSTED_DATA, + "B302": issue.Cwe.DESERIALIZATION_OF_UNTRUSTED_DATA, + "B303": issue.Cwe.BROKEN_CRYPTO, + "B304": issue.Cwe.BROKEN_CRYPTO, + "B305": issue.Cwe.BROKEN_CRYPTO, + "B306": issue.Cwe.INSECURE_TEMP_FILE, + "B307": issue.Cwe.OS_COMMAND_INJECTION, + "B308": issue.Cwe.XSS, + "B309": issue.Cwe.CLEARTEXT_TRANSMISSION, + "B310": issue.Cwe.PATH_TRAVERSAL, + "B311": issue.Cwe.INSUFFICIENT_RANDOM_VALUES, + "B312": issue.Cwe.CLEARTEXT_TRANSMISSION, + "B313": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B314": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B315": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B316": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B317": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B318": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B319": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B320": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B321": issue.Cwe.CLEARTEXT_TRANSMISSION, + "B322": issue.Cwe.OS_COMMAND_INJECTION, + "B323": issue.Cwe.IMPROPER_CERT_VALIDATION, + "B325": issue.Cwe.INSECURE_TEMP_FILE, + # Imports + "B401": issue.Cwe.CLEARTEXT_TRANSMISSION, + "B402": issue.Cwe.CLEARTEXT_TRANSMISSION, + "B403": issue.Cwe.DESERIALIZATION_OF_UNTRUSTED_DATA, + "B404": issue.Cwe.OS_COMMAND_INJECTION, + "B405": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B406": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B407": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B408": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B409": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B410": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B411": issue.Cwe.IMPROPER_INPUT_VALIDATION, + "B412": issue.Cwe.IMPROPER_ACCESS_CONTROL, + "B413": issue.Cwe.BROKEN_CRYPTO, + "B414": issue.Cwe.BROKEN_CRYPTO, +} diff --git a/bandit/core/issue.py b/bandit/core/issue.py index ef997b6d0..e9727a001 100644 --- a/bandit/core/issue.py +++ b/bandit/core/issue.py @@ -7,10 +7,77 @@ from bandit.core import constants +class Cwe: + NOTSET = 0 + IMPROPER_INPUT_VALIDATION = 20 + PATH_TRAVERSAL = 22 + OS_COMMAND_INJECTION = 78 + XSS = 79 + BASIC_XSS = 80 + SQL_INJECTION = 89 + CODE_INJECTION = 94 + IMPROPER_WILDCARD_NEUTRALIZATION = 155 + HARD_CODED_PASSWORD = 259 + IMPROPER_ACCESS_CONTROL = 284 + IMPROPER_CERT_VALIDATION = 295 + CLEARTEXT_TRANSMISSION = 319 + INADEQUATE_ENCRYPTION_STRENGTH = 326 + BROKEN_CRYPTO = 327 + INSUFFICIENT_RANDOM_VALUES = 330 + INSECURE_TEMP_FILE = 377 + DESERIALIZATION_OF_UNTRUSTED_DATA = 502 + MULTIPLE_BINDS = 605 + IMPROPER_CHECK_OF_EXCEPT_COND = 703 + INCORRECT_PERMISSION_ASSIGNMENT = 732 + + MITRE_URL_PATTERN = "https://cwe.mitre.org/data/definitions/%s.html" + + def __init__(self, id=NOTSET): + self.id = id + + def link(self): + if self.id == Cwe.NOTSET: + return "" + + return Cwe.MITRE_URL_PATTERN % str(self.id) + + def __str__(self): + if self.id == Cwe.NOTSET: + return "" + + return "CWE-%i (%s)" % (self.id, self.link()) + + def as_dict(self): + return ( + {"id": self.id, "link": self.link()} + if self.id != Cwe.NOTSET + else {} + ) + + def as_jsons(self): + return str(self.as_dict()) + + def from_dict(self, data): + if "id" in data: + self.id = int(data["id"]) + else: + self.id = Cwe.NOTSET + + def __eq__(self, other): + return self.id == other.id + + def __ne__(self, other): + return self.id != other.id + + def __hash__(self): + return id(self) + + class Issue: def __init__( self, severity, + cwe=0, confidence=constants.CONFIDENCE_DEFAULT, text="", ident=None, @@ -19,6 +86,7 @@ def __init__( col_offset=0, ): self.severity = severity + self.cwe = Cwe(cwe) self.confidence = confidence if isinstance(text, bytes): text = text.decode("utf-8") @@ -33,11 +101,13 @@ def __init__( def __str__(self): return ( - "Issue: '%s' from %s:%s: Severity: %s Confidence: " "%s at %s:%i" + "Issue: '%s' from %s:%s: CWE: %s, Severity: %s Confidence: " + "%s at %s:%i" ) % ( self.text, self.test_id, (self.ident or self.test), + str(self.cwe), self.severity, self.confidence, self.fname, @@ -50,6 +120,7 @@ def __eq__(self, other): match_types = [ "text", "severity", + "cwe", "confidence", "fname", "test", @@ -119,6 +190,7 @@ def as_dict(self, with_code=True): "test_name": self.test, "test_id": self.test_id, "issue_severity": self.severity, + "issue_cwe": self.cwe.as_dict(), "issue_confidence": self.confidence, "issue_text": self.text.encode("utf-8").decode("utf-8"), "line_number": self.lineno, @@ -134,6 +206,7 @@ def from_dict(self, data, with_code=True): self.code = data["code"] self.fname = data["filename"] self.severity = data["issue_severity"] + self.cwe = cwe_from_dict(data["issue_cwe"]) self.confidence = data["issue_confidence"] self.text = data["issue_text"] self.test = data["test_name"] @@ -143,6 +216,12 @@ def from_dict(self, data, with_code=True): self.col_offset = data.get("col_offset", 0) +def cwe_from_dict(data): + cwe = Cwe() + cwe.from_dict(data) + return cwe + + def issue_from_dict(data): i = Issue(severity=data["issue_severity"]) i.from_dict(data) diff --git a/bandit/formatters/csv.py b/bandit/formatters/csv.py index f0dbc08bf..81aa747db 100644 --- a/bandit/formatters/csv.py +++ b/bandit/formatters/csv.py @@ -54,6 +54,7 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1): "test_name", "test_id", "issue_severity", + "issue_cwe", "issue_confidence", "issue_text", "line_number", @@ -68,6 +69,7 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1): writer.writeheader() for result in results: r = result.as_dict(with_code=False) + r["issue_cwe"] = r["issue_cwe"]["link"] r["more_info"] = docs_utils.get_url(r["test_id"]) writer.writerow(r) diff --git a/bandit/formatters/html.py b/bandit/formatters/html.py index 93262b4ef..ff6ea1f3e 100644 --- a/bandit/formatters/html.py +++ b/bandit/formatters/html.py @@ -258,6 +258,7 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1): {test_name}: {test_text}
Test ID: {test_id}
Severity: {severity}
+ CWE: {cwe}
Confidence: {confidence}
File: {path}
Line number: {line_number}
@@ -357,6 +358,7 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1): test_id=issue.test_id, test_text=issue.text, severity=issue.severity, + cwe=issue.cwe, confidence=issue.confidence, path=issue.fname, code=code, diff --git a/bandit/formatters/screen.py b/bandit/formatters/screen.py index 0573b24ac..c1e204382 100644 --- a/bandit/formatters/screen.py +++ b/bandit/formatters/screen.py @@ -111,8 +111,13 @@ def _output_issue_str( ) bits.append( - "%s Severity: %s Confidence: %s" - % (indent, issue.severity.capitalize(), issue.confidence.capitalize()) + "%s Severity: %s CWE: %s Confidence: %s" + % ( + indent, + issue.severity.capitalize(), + str(issue.cwe), + issue.confidence.capitalize(), + ) ) bits.append( diff --git a/bandit/formatters/text.py b/bandit/formatters/text.py index ec8e0c8bf..3e821d1d6 100644 --- a/bandit/formatters/text.py +++ b/bandit/formatters/text.py @@ -79,8 +79,13 @@ def _output_issue_str( ) bits.append( - "%s Severity: %s Confidence: %s" - % (indent, issue.severity.capitalize(), issue.confidence.capitalize()) + "%s Severity: %s CWE: %s Confidence: %s" + % ( + indent, + issue.severity.capitalize(), + str(issue.cwe), + issue.confidence.capitalize(), + ) ) bits.append( diff --git a/bandit/formatters/xml.py b/bandit/formatters/xml.py index f3c2bc2ed..f3f9421bb 100644 --- a/bandit/formatters/xml.py +++ b/bandit/formatters/xml.py @@ -55,10 +55,14 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1): root, "testcase", classname=issue.fname, name=test ) - text = "Test ID: %s Severity: %s Confidence: %s\n%s\nLocation %s:%s" + text = ( + "Test ID: %s Severity: %s CWE: %s Confidence: %s\n%s\n" + "Location %s:%s" + ) text = text % ( issue.test_id, issue.severity, + issue.cwe, issue.confidence, issue.text, issue.fname, diff --git a/bandit/plugins/app_debug.py b/bandit/plugins/app_debug.py index ae66459f3..3fbfd7db0 100644 --- a/bandit/plugins/app_debug.py +++ b/bandit/plugins/app_debug.py @@ -37,6 +37,7 @@ """ # noqa: E501 import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -48,6 +49,7 @@ def flask_debug_true(context): if context.check_call_arg_value("debug", "True"): return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B201"], confidence=bandit.MEDIUM, text="A Flask app appears to be run with debug=True, " "which exposes the Werkzeug debugger and allows " diff --git a/bandit/plugins/asserts.py b/bandit/plugins/asserts.py index 7057873eb..4d401c9c5 100644 --- a/bandit/plugins/asserts.py +++ b/bandit/plugins/asserts.py @@ -50,6 +50,7 @@ import fnmatch import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -68,6 +69,7 @@ def assert_used(context, config): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B101"], confidence=bandit.HIGH, text=( "Use of assert detected. The enclosed code " diff --git a/bandit/plugins/crypto_request_no_cert_validation.py b/bandit/plugins/crypto_request_no_cert_validation.py index eed10ecf5..fc8da08d1 100644 --- a/bandit/plugins/crypto_request_no_cert_validation.py +++ b/bandit/plugins/crypto_request_no_cert_validation.py @@ -39,6 +39,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -53,6 +54,7 @@ def request_with_no_cert_validation(context): if context.check_call_arg_value("verify", "False"): issue = bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B501"], confidence=bandit.HIGH, text="Requests call with verify=False disabling SSL " "certificate checks, security issue.", diff --git a/bandit/plugins/django_sql_injection.py b/bandit/plugins/django_sql_injection.py index 457bf8251..217accf4a 100644 --- a/bandit/plugins/django_sql_injection.py +++ b/bandit/plugins/django_sql_injection.py @@ -5,6 +5,7 @@ import ast import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -74,6 +75,7 @@ def django_extra_used(context): if insecure: return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B611"], confidence=bandit.MEDIUM, text=description, ) @@ -99,6 +101,7 @@ def django_rawsql_used(context): if not isinstance(sql, ast.Str): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B611"], confidence=bandit.MEDIUM, text=description, ) diff --git a/bandit/plugins/django_xss.py b/bandit/plugins/django_xss.py index 952d16fae..c46ef4074 100644 --- a/bandit/plugins/django_xss.py +++ b/bandit/plugins/django_xss.py @@ -5,6 +5,7 @@ import ast import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -219,7 +220,10 @@ def check_risk(node): if not secure: return bandit.Issue( - severity=bandit.MEDIUM, confidence=bandit.HIGH, text=description + severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B703"], + confidence=bandit.HIGH, + text=description, ) diff --git a/bandit/plugins/exec.py b/bandit/plugins/exec.py index d42918887..3f89e035c 100644 --- a/bandit/plugins/exec.py +++ b/bandit/plugins/exec.py @@ -30,12 +30,14 @@ .. versionadded:: 0.9.0 """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test def exec_issue(): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B102"], confidence=bandit.HIGH, text="Use of exec detected.", ) diff --git a/bandit/plugins/general_bad_file_permissions.py b/bandit/plugins/general_bad_file_permissions.py index 03b20f72b..f8b0ac421 100644 --- a/bandit/plugins/general_bad_file_permissions.py +++ b/bandit/plugins/general_bad_file_permissions.py @@ -47,6 +47,7 @@ import stat import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -73,6 +74,7 @@ def set_bad_file_permissions(context): filename = "NOT PARSED" return bandit.Issue( severity=sev_level, + cwe=cwemap.CWEMAP["B103"], confidence=bandit.HIGH, text="Chmod setting a permissive mask %s on file (%s)." % (oct(mode), filename), diff --git a/bandit/plugins/general_bind_all_interfaces.py b/bandit/plugins/general_bind_all_interfaces.py index ffdd02c04..3b53b8e5a 100644 --- a/bandit/plugins/general_bind_all_interfaces.py +++ b/bandit/plugins/general_bind_all_interfaces.py @@ -31,6 +31,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -40,6 +41,7 @@ def hardcoded_bind_all_interfaces(context): if context.string_val == "0.0.0.0": return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B104"], confidence=bandit.MEDIUM, text="Possible binding to all interfaces.", ) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 2450adccd..930e8017d 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -6,6 +6,7 @@ import re import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -18,6 +19,7 @@ def _report(value): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B105"], confidence=bandit.MEDIUM, text=("Possible hardcoded password: '%s'" % value), ) diff --git a/bandit/plugins/general_hardcoded_tmp.py b/bandit/plugins/general_hardcoded_tmp.py index bf42f7fab..c12762e8c 100644 --- a/bandit/plugins/general_hardcoded_tmp.py +++ b/bandit/plugins/general_hardcoded_tmp.py @@ -48,6 +48,7 @@ """ # noqa: E501 import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -68,6 +69,7 @@ def hardcoded_tmp_directory(context, config): if any(context.string_val.startswith(s) for s in tmp_dirs): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B108"], confidence=bandit.MEDIUM, text="Probable insecure usage of temp file/directory.", ) diff --git a/bandit/plugins/hashlib_new_insecure_functions.py b/bandit/plugins/hashlib_new_insecure_functions.py index 3d3e7ac98..35b5d7338 100644 --- a/bandit/plugins/hashlib_new_insecure_functions.py +++ b/bandit/plugins/hashlib_new_insecure_functions.py @@ -28,6 +28,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -49,6 +50,7 @@ def hashlib_new(context): ): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B324"], confidence=bandit.HIGH, text="Use of insecure MD4 or MD5 hash function.", lineno=context.node.lineno, diff --git a/bandit/plugins/injection_paramiko.py b/bandit/plugins/injection_paramiko.py index 270f46b26..e92b3049d 100644 --- a/bandit/plugins/injection_paramiko.py +++ b/bandit/plugins/injection_paramiko.py @@ -36,6 +36,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -51,6 +52,7 @@ def paramiko_calls(context): if context.call_function_name in ["exec_command"]: return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B601"], confidence=bandit.MEDIUM, text=issue_text, ) diff --git a/bandit/plugins/injection_shell.py b/bandit/plugins/injection_shell.py index 62d14e99b..8a6fb1591 100644 --- a/bandit/plugins/injection_shell.py +++ b/bandit/plugins/injection_shell.py @@ -6,8 +6,10 @@ import re import bandit +from bandit.core import cwemap from bandit.core import test_properties as test + # yuck, regex: starts with a windows drive letter (eg C:) # or one of our path delimeter characters (/, \, .) full_path_match = re.compile(r"^(?:[A-Za-z](?=\:)|[\\\/\.])") @@ -196,6 +198,7 @@ def subprocess_popen_with_shell_equals_true(context, config): if sev == bandit.LOW: return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B602"], confidence=bandit.HIGH, text="subprocess call with shell=True seems safe, but " "may be changed in the future, consider " @@ -205,6 +208,7 @@ def subprocess_popen_with_shell_equals_true(context, config): else: return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B602"], confidence=bandit.HIGH, text="subprocess call with shell=True identified, " "security issue.", @@ -284,6 +288,7 @@ def subprocess_without_shell_equals_true(context, config): if not has_shell(context): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B603"], confidence=bandit.HIGH, text="subprocess call - check for execution of untrusted " "input.", @@ -362,6 +367,7 @@ def any_other_function_with_shell_equals_true(context, config): if has_shell(context): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B604"], confidence=bandit.LOW, text="Function call with shell=True parameter identified, " "possible security issue.", @@ -448,6 +454,7 @@ def start_process_with_a_shell(context, config): if sev == bandit.LOW: return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B605"], confidence=bandit.HIGH, text="Starting a process with a shell: " "Seems safe, but may be changed in the future, " @@ -456,6 +463,7 @@ def start_process_with_a_shell(context, config): else: return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B605"], confidence=bandit.HIGH, text="Starting a process with a shell, possible injection" " detected, security issue.", @@ -544,6 +552,7 @@ def start_process_with_no_shell(context, config): if config and context.call_function_name_qual in config["no_shell"]: return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B606"], confidence=bandit.MEDIUM, text="Starting a process without a shell.", ) @@ -641,6 +650,7 @@ def start_process_with_partial_path(context, config): if isinstance(node, ast.Str) and not full_path_match.match(node.s): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B607"], confidence=bandit.HIGH, text="Starting a process with a partial executable path", ) diff --git a/bandit/plugins/injection_sql.py b/bandit/plugins/injection_sql.py index 7f7b4ccf5..712992c28 100644 --- a/bandit/plugins/injection_sql.py +++ b/bandit/plugins/injection_sql.py @@ -52,6 +52,7 @@ import re import bandit +from bandit.core import cwemap from bandit.core import test_properties as test from bandit.core import utils @@ -104,6 +105,7 @@ def hardcoded_sql_expressions(context): if _check_string(val[1]): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B608"], confidence=bandit.MEDIUM if val[0] else bandit.LOW, text="Possible SQL injection vector through string-based " "query construction.", diff --git a/bandit/plugins/injection_wildcard.py b/bandit/plugins/injection_wildcard.py index 0da922c48..c105e591f 100644 --- a/bandit/plugins/injection_wildcard.py +++ b/bandit/plugins/injection_wildcard.py @@ -94,10 +94,10 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test from bandit.plugins import injection_shell # NOTE(tkelsey): shared config - gen_config = injection_shell.gen_config @@ -130,6 +130,7 @@ def linux_commands_wildcard_injection(context, config): ): return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B609"], confidence=bandit.MEDIUM, text="Possible wildcard injection in call: %s" % context.call_function_name_qual, diff --git a/bandit/plugins/insecure_ssl_tls.py b/bandit/plugins/insecure_ssl_tls.py index c2d750839..bc09f0955 100644 --- a/bandit/plugins/insecure_ssl_tls.py +++ b/bandit/plugins/insecure_ssl_tls.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: Apache-2.0 import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -105,6 +106,7 @@ def ssl_with_bad_version(context, config): if context.check_call_arg_value("ssl_version", bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B502"], confidence=bandit.HIGH, text="ssl.wrap_socket call with insecure SSL/TLS protocol " "version identified, security issue.", @@ -114,6 +116,7 @@ def ssl_with_bad_version(context, config): if context.check_call_arg_value("method", bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B502"], confidence=bandit.HIGH, text="SSL.Context call with insecure SSL/TLS protocol " "version identified, security issue.", @@ -132,6 +135,7 @@ def ssl_with_bad_version(context, config): ) or context.get_lineno_for_call_arg("ssl_version") return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B502"], confidence=bandit.MEDIUM, text="Function call with insecure SSL/TLS protocol " "identified, possible security issue.", @@ -189,6 +193,7 @@ def ssl_with_bad_defaults(context, config): if val in bad_ssl_versions: return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B503"], confidence=bandit.MEDIUM, text="Function definition identified with insecure SSL/TLS " "protocol version by default, possible security " @@ -247,6 +252,7 @@ def ssl_with_no_version(context): # tests for that (ssl_version is not specified). return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B504"], confidence=bandit.MEDIUM, text="ssl.wrap_socket call with no SSL/TLS protocol version " "specified, the default SSLv23 could be insecure, " diff --git a/bandit/plugins/jinja2_templates.py b/bandit/plugins/jinja2_templates.py index d79600e9e..0edc38316 100644 --- a/bandit/plugins/jinja2_templates.py +++ b/bandit/plugins/jinja2_templates.py @@ -60,6 +60,7 @@ import ast import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -80,6 +81,7 @@ def jinja2_autoescape_false(context): ): return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B701"], confidence=bandit.HIGH, text="Using jinja2 templates with autoescape=" "False is dangerous and can lead to XSS. " @@ -105,6 +107,7 @@ def jinja2_autoescape_false(context): else: return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B701"], confidence=bandit.MEDIUM, text="Using jinja2 templates with autoescape=" "False is dangerous and can lead to XSS. " @@ -116,6 +119,7 @@ def jinja2_autoescape_false(context): # behavior return bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B701"], confidence=bandit.HIGH, text="By default, jinja2 sets autoescape to False. Consider " "using autoescape=True or use the select_autoescape " diff --git a/bandit/plugins/mako_templates.py b/bandit/plugins/mako_templates.py index e25b10a13..42b96160a 100644 --- a/bandit/plugins/mako_templates.py +++ b/bandit/plugins/mako_templates.py @@ -38,6 +38,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -53,6 +54,7 @@ def use_of_mako_templates(context): # feature and thus each variable must be carefully sanitized. return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B702"], confidence=bandit.HIGH, text="Mako templates allow HTML/JS rendering by default and " "are inherently open to XSS attacks. Ensure variables " diff --git a/bandit/plugins/ssh_no_host_key_verification.py b/bandit/plugins/ssh_no_host_key_verification.py index 88be94fe2..031f0e8a6 100644 --- a/bandit/plugins/ssh_no_host_key_verification.py +++ b/bandit/plugins/ssh_no_host_key_verification.py @@ -32,6 +32,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -48,6 +49,7 @@ def ssh_no_host_key_verification(context): ]: issue = bandit.Issue( severity=bandit.HIGH, + cwe=cwemap.CWEMAP["B507"], confidence=bandit.MEDIUM, text="Paramiko call with policy set to automatically trust " "the unknown host key.", diff --git a/bandit/plugins/try_except_continue.py b/bandit/plugins/try_except_continue.py index 170f53c72..854fe6fc9 100644 --- a/bandit/plugins/try_except_continue.py +++ b/bandit/plugins/try_except_continue.py @@ -72,6 +72,7 @@ class (or no type). To accommodate this, the test may be configured to ignore import ast import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -96,6 +97,7 @@ def try_except_continue(context, config): if isinstance(node.body[0], ast.Continue): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B112"], confidence=bandit.HIGH, text=("Try, Except, Continue detected."), ) diff --git a/bandit/plugins/try_except_pass.py b/bandit/plugins/try_except_pass.py index 5aae7986d..2ca64b214 100644 --- a/bandit/plugins/try_except_pass.py +++ b/bandit/plugins/try_except_pass.py @@ -70,6 +70,7 @@ class (or no type). To accommodate this, the test may be configured to ignore import ast import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -94,6 +95,7 @@ def try_except_pass(context, config): if isinstance(node.body[0], ast.Pass): return bandit.Issue( severity=bandit.LOW, + cwe=cwemap.CWEMAP["B110"], confidence=bandit.HIGH, text=("Try, Except, Pass detected."), ) diff --git a/bandit/plugins/weak_cryptographic_key.py b/bandit/plugins/weak_cryptographic_key.py index aafc1e45c..8e5dc1f94 100644 --- a/bandit/plugins/weak_cryptographic_key.py +++ b/bandit/plugins/weak_cryptographic_key.py @@ -35,6 +35,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -74,6 +75,7 @@ def _classify_key_size(config, key_type, key_size): if key_size < size: return bandit.Issue( severity=level, + cwe=cwemap.CWEMAP["B505"], confidence=bandit.HIGH, text="%s key sizes below %d bits are considered breakable. " % (key_type, size), diff --git a/bandit/plugins/yaml_load.py b/bandit/plugins/yaml_load.py index 2077790f5..3bc6603b9 100644 --- a/bandit/plugins/yaml_load.py +++ b/bandit/plugins/yaml_load.py @@ -36,6 +36,7 @@ """ import bandit +from bandit.core import cwemap from bandit.core import test_properties as test @@ -59,6 +60,7 @@ def yaml_load(context): ): return bandit.Issue( severity=bandit.MEDIUM, + cwe=cwemap.CWEMAP["B506"], confidence=bandit.HIGH, text="Use of unsafe yaml load. Allows instantiation of" " arbitrary objects. Consider yaml.safe_load().", diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 39336eb48..ab2ada95e 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -765,6 +765,10 @@ def test_baseline_filter(self): "filename": "{}/examples/flask_debug.py", "issue_confidence": "MEDIUM", "issue_severity": "HIGH", + "issue_cwe": {{ + "id": 94, + "link": "https://cwe.mitre.org/data/definitions/94.html" + }}, "issue_text": "{}", "line_number": 10, "col_offset": 0, diff --git a/tests/unit/core/test_blacklisting.py b/tests/unit/core/test_blacklisting.py index 4aed78481..5e177602a 100644 --- a/tests/unit/core/test_blacklisting.py +++ b/tests/unit/core/test_blacklisting.py @@ -16,6 +16,7 @@ def test_report_issue(self): self.assertIsInstance(issue_dict, dict) self.assertEqual("B000", issue_dict["test_id"]) self.assertEqual("HIGH", issue_dict["issue_severity"]) + self.assertEqual({}, issue_dict["issue_cwe"]) self.assertEqual("HIGH", issue_dict["issue_confidence"]) self.assertEqual("test name", issue_dict["issue_text"]) @@ -27,5 +28,6 @@ def test_report_issue_defaults(self): self.assertIsInstance(issue_dict, dict) self.assertEqual("LEGACY", issue_dict["test_id"]) self.assertEqual("MEDIUM", issue_dict["issue_severity"]) + self.assertEqual({}, issue_dict["issue_cwe"]) self.assertEqual("HIGH", issue_dict["issue_confidence"]) self.assertEqual("test name", issue_dict["issue_text"]) diff --git a/tests/unit/core/test_issue.py b/tests/unit/core/test_issue.py index 66853cf0c..dd1c72b9c 100644 --- a/tests/unit/core/test_issue.py +++ b/tests/unit/core/test_issue.py @@ -18,12 +18,15 @@ def test_issue_create(self): def test_issue_str(self): test_issue = _get_issue_instance() + expect = ( + "Issue: 'Test issue' from B999:bandit_plugin:" + " CWE: %s," + " Severity: MEDIUM " + "Confidence: MEDIUM at code.py:1" + ) + self.assertEqual( - ( - "Issue: 'Test issue' from B999:bandit_plugin: Severity: MEDIUM" - " Confidence: MEDIUM at code.py:1" - ), - str(test_issue), + expect % str(issue.Cwe(issue.Cwe.MULTIPLE_BINDS)), str(test_issue) ) def test_issue_as_dict(self): @@ -108,7 +111,9 @@ def test_matches_issue(self): @mock.patch("linecache.getline") def test_get_code(self, getline): getline.return_value = b"\x08\x30" - new_issue = issue.Issue(bandit.MEDIUM, lineno=1) + new_issue = issue.Issue( + bandit.MEDIUM, cwe=issue.Cwe.MULTIPLE_BINDS, lineno=1 + ) try: new_issue.get_code() @@ -116,8 +121,12 @@ def test_get_code(self, getline): self.fail("Bytes not properly decoded in issue.get_code()") -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, "Test issue") +def _get_issue_instance( + severity=bandit.MEDIUM, + cwe=issue.Cwe.MULTIPLE_BINDS, + confidence=bandit.MEDIUM, +): + new_issue = issue.Issue(severity, cwe, confidence, "Test issue") new_issue.fname = "code.py" new_issue.test = "bandit_plugin" new_issue.test_id = "B999" diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py index 616e6cc9c..b507d104c 100644 --- a/tests/unit/core/test_manager.py +++ b/tests/unit/core/test_manager.py @@ -15,8 +15,13 @@ class ManagerTests(testtools.TestCase): - def _get_issue_instance(self, sev=constants.MEDIUM, conf=constants.MEDIUM): - new_issue = issue.Issue(sev, conf, "Test issue") + def _get_issue_instance( + self, + sev=constants.MEDIUM, + cwe=issue.Cwe.MULTIPLE_BINDS, + conf=constants.MEDIUM, + ): + new_issue = issue.Issue(sev, cwe, conf, "Test issue") new_issue.fname = "code.py" new_issue.test = "bandit_plugin" new_issue.lineno = 1 @@ -130,6 +135,10 @@ def test_populate_baseline_success(self): "code": "test code", "filename": "example_file.py", "issue_severity": "low", + "issue_cwe": { + "id": 605, + "link": "%s" + }, "issue_confidence": "low", "issue_text": "test issue", "test_name": "some_test", @@ -139,11 +148,14 @@ def test_populate_baseline_success(self): } ] } - """ + """ % ( + "https://cwe.mitre.org/data/definitions/605.html" + ) issue_dictionary = { "code": "test code", "filename": "example_file.py", "issue_severity": "low", + "issue_cwe": issue.Cwe(issue.Cwe.MULTIPLE_BINDS).as_dict(), "issue_confidence": "low", "issue_text": "test issue", "test_name": "some_test", @@ -167,7 +179,10 @@ def test_populate_baseline_invalid_json(self, mock_logger_warning): def test_results_count(self): levels = [constants.LOW, constants.MEDIUM, constants.HIGH] self.manager.results = [ - issue.Issue(severity=level, confidence=level) for level in levels + issue.Issue( + severity=level, cwe=issue.Cwe.MULTIPLE_BINDS, confidence=level + ) + for level in levels ] r = [ diff --git a/tests/unit/formatters/test_csv.py b/tests/unit/formatters/test_csv.py index 77dc9bc48..fd9166e30 100644 --- a/tests/unit/formatters/test_csv.py +++ b/tests/unit/formatters/test_csv.py @@ -26,7 +26,10 @@ def setUp(self): } self.check_name = "hardcoded_bind_all_interfaces" self.issue = issue.Issue( - bandit.MEDIUM, bandit.MEDIUM, "Possible binding to all interfaces." + bandit.MEDIUM, + 123, + bandit.MEDIUM, + "Possible binding to all interfaces.", ) self.manager.out_file = self.tmp_fname diff --git a/tests/unit/formatters/test_custom.py b/tests/unit/formatters/test_custom.py index 16335d13f..908ddb3e7 100644 --- a/tests/unit/formatters/test_custom.py +++ b/tests/unit/formatters/test_custom.py @@ -25,7 +25,9 @@ def setUp(self): } self.check_name = "hardcoded_bind_all_interfaces" self.issue = issue.Issue( - bandit.MEDIUM, bandit.MEDIUM, "Possible binding to all interfaces." + bandit.MEDIUM, + bandit.MEDIUM, + text="Possible binding to all interfaces.", ) self.manager.out_file = self.tmp_fname diff --git a/tests/unit/formatters/test_html.py b/tests/unit/formatters/test_html.py index f115e146d..07e6bd0b4 100644 --- a/tests/unit/formatters/test_html.py +++ b/tests/unit/formatters/test_html.py @@ -149,8 +149,10 @@ def test_escaping(self, get_issue_list, get_code): self.assertNotIn(marker, contents) -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, "Test issue") +def _get_issue_instance( + severity=bandit.MEDIUM, cwe=123, confidence=bandit.MEDIUM +): + new_issue = issue.Issue(severity, cwe, confidence, "Test issue") new_issue.fname = "code.py" new_issue.test = "bandit_plugin" new_issue.lineno = 1 diff --git a/tests/unit/formatters/test_json.py b/tests/unit/formatters/test_json.py index 454faa300..ddc1cde51 100644 --- a/tests/unit/formatters/test_json.py +++ b/tests/unit/formatters/test_json.py @@ -30,12 +30,27 @@ def setUp(self): } self.check_name = "hardcoded_bind_all_interfaces" self.issue = issue.Issue( - bandit.MEDIUM, bandit.MEDIUM, "Possible binding to all interfaces." + bandit.MEDIUM, + issue.Cwe.MULTIPLE_BINDS, + bandit.MEDIUM, + "Possible binding to all interfaces.", ) self.candidates = [ - issue.Issue(bandit.LOW, bandit.LOW, "Candidate A", lineno=1), - issue.Issue(bandit.HIGH, bandit.HIGH, "Candiate B", lineno=2), + issue.Issue( + issue.Cwe.MULTIPLE_BINDS, + bandit.LOW, + bandit.LOW, + "Candidate A", + lineno=1, + ), + issue.Issue( + bandit.HIGH, + issue.Cwe.MULTIPLE_BINDS, + bandit.HIGH, + "Candiate B", + lineno=2, + ), ] self.manager.out_file = self.tmp_fname diff --git a/tests/unit/formatters/test_screen.py b/tests/unit/formatters/test_screen.py index cdeeeb34d..e2d420aed 100644 --- a/tests/unit/formatters/test_screen.py +++ b/tests/unit/formatters/test_screen.py @@ -35,9 +35,10 @@ def _template(_issue, _indent_val, _code, _color): _issue.test, _issue.text, ), - "{} Severity: {} Confidence: {}".format( + "{} Severity: {} CWE: {} Confidence: {}".format( _indent_val, _issue.severity.capitalize(), + _issue.cwe, _issue.confidence.capitalize(), ), "{} Location: {}:{}:{}".format( @@ -232,8 +233,10 @@ def test_report_baseline(self, get_issue_list): output_str.assert_has_calls(calls, any_order=True) -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, "Test issue") +def _get_issue_instance( + severity=bandit.MEDIUM, cwe=123, confidence=bandit.MEDIUM +): + new_issue = issue.Issue(severity, cwe, confidence, "Test issue") new_issue.fname = "code.py" new_issue.test = "bandit_plugin" new_issue.lineno = 1 diff --git a/tests/unit/formatters/test_text.py b/tests/unit/formatters/test_text.py index a55172d0b..2ce80d499 100644 --- a/tests/unit/formatters/test_text.py +++ b/tests/unit/formatters/test_text.py @@ -31,9 +31,10 @@ def _template(_issue, _indent_val, _code): "{}>> Issue: [{}:{}] {}".format( _indent_val, _issue.test_id, _issue.test, _issue.text ), - "{} Severity: {} Confidence: {}".format( + "{} Severity: {} CWE: {} Confidence: {}".format( _indent_val, _issue.severity.capitalize(), + _issue.cwe, _issue.confidence.capitalize(), ), "{} Location: {}:{}:{}".format( @@ -143,6 +144,7 @@ def test_report_nobaseline(self, get_issue_list): "binding.py (score: ", "CONFIDENCE: 1", "SEVERITY: 1", + "CWE: %s" % str(issue.Cwe(issue.Cwe.MULTIPLE_BINDS)), "Files excluded (1):", "def.py", "Undefined: 1", @@ -202,8 +204,12 @@ def test_report_baseline(self, get_issue_list): output_str.assert_has_calls(calls, any_order=True) -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, "Test issue") +def _get_issue_instance( + severity=bandit.MEDIUM, + cwe=issue.Cwe.MULTIPLE_BINDS, + confidence=bandit.MEDIUM, +): + new_issue = issue.Issue(severity, cwe, confidence, "Test issue") new_issue.fname = "code.py" new_issue.test = "bandit_plugin" new_issue.lineno = 1 diff --git a/tests/unit/formatters/test_xml.py b/tests/unit/formatters/test_xml.py index c2c06d3f6..64e271882 100644 --- a/tests/unit/formatters/test_xml.py +++ b/tests/unit/formatters/test_xml.py @@ -27,7 +27,10 @@ def setUp(self): } self.check_name = "hardcoded_bind_all_interfaces" self.issue = issue.Issue( - bandit.MEDIUM, bandit.MEDIUM, "Possible binding to all interfaces." + bandit.MEDIUM, + issue.Cwe.MULTIPLE_BINDS, + bandit.MEDIUM, + "Possible binding to all interfaces.", ) self.manager.out_file = self.tmp_fname diff --git a/tests/unit/formatters/test_yaml.py b/tests/unit/formatters/test_yaml.py index bca249c78..30df3851d 100644 --- a/tests/unit/formatters/test_yaml.py +++ b/tests/unit/formatters/test_yaml.py @@ -30,12 +30,15 @@ def setUp(self): } self.check_name = "hardcoded_bind_all_interfaces" self.issue = issue.Issue( - bandit.MEDIUM, bandit.MEDIUM, "Possible binding to all interfaces." + bandit.MEDIUM, + 123, + bandit.MEDIUM, + "Possible binding to all interfaces.", ) self.candidates = [ - issue.Issue(bandit.LOW, bandit.LOW, "Candidate A", lineno=1), - issue.Issue(bandit.HIGH, bandit.HIGH, "Candiate B", lineno=2), + issue.Issue(bandit.LOW, 123, bandit.LOW, "Candidate A", lineno=1), + issue.Issue(bandit.HIGH, 123, bandit.HIGH, "Candiate B", lineno=2), ] self.manager.out_file = self.tmp_fname