def _hashlib_func(context): if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split(".") if "hashlib" in qualname_list: func = qualname_list[-1] keywords = context.call_keywords if func in WEAK_HASHES: if keywords.get("usedforsecurity", "True") == "True": return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, cwe=issue.Cwe.BROKEN_CRYPTO, text=f"Use of weak {func.upper()} hash for security. " "Consider usedforsecurity=False", lineno=context.node.lineno, ) elif func == "new": args = context.call_args name = args[0] if args else keywords.get("name", None) if isinstance(name, str) and name.lower() in WEAK_HASHES: if keywords.get("usedforsecurity", "True") == "True": return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, cwe=issue.Cwe.BROKEN_CRYPTO, text=f"Use of weak {name.upper()} hash for " "security. Consider usedforsecurity=False", lineno=context.node.lineno, )
def exec_issue(level, members=""): if level == bandit.LOW: return bandit.Issue( severity=bandit.LOW, confidence=bandit.LOW, cwe=issue.Cwe.PATH_TRAVERSAL, text="Usage of tarfile.extractall(members=function(tarfile)). " "Make sure your function properly discards dangerous members " "{members}).".format(members=members), ) elif level == bandit.MEDIUM: return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, cwe=issue.Cwe.PATH_TRAVERSAL, text="Found tarfile.extractall(members=?) but couldn't " "identify the type of members. " "Check if the members were properly validated " "{members}).".format(members=members), ) else: return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, cwe=issue.Cwe.PATH_TRAVERSAL, text="tarfile.extractall used without any validation. " "Please check and discard dangerous members.", )
def ssl_with_bad_version(context, config): bad_ssl_versions = get_bad_proto_versions(config) if (context.call_function_name_qual == 'ssl.wrap_socket'): if context.check_call_arg_value('ssl_version', bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, text="ssl.wrap_socket call with insecure SSL/TLS protocol " "version identified, security issue. %s" % context.call_args_string) elif (context.call_function_name_qual == 'pyOpenSSL.SSL.Context'): if context.check_call_arg_value('method', bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, text="SSL.Context call with insecure SSL/TLS protocol " "version identified, security issue. %s" % context.call_args_string) elif (context.call_function_name_qual != 'ssl.wrap_socket' and context.call_function_name_qual != 'pyOpenSSL.SSL.Context'): if (context.check_call_arg_value('method', bad_ssl_versions) or context.check_call_arg_value('ssl_version', bad_ssl_versions)): return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, text="Function call with insecure SSL/TLS protocol " "identified, possible security issue. %s" % context.call_args_string)
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'), )
def jinja2_autoescape_false(context): # check type just to be safe if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split(".") func = qualname_list[-1] if "jinja2" in qualname_list and func == "Environment": for node in ast.walk(context.node): if isinstance(node, ast.keyword): # definite autoescape = False if getattr(node, "arg", None) == "autoescape" and ( getattr(node.value, "id", None) == "False" or getattr(node.value, "value", None) is False ): 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. " "Use autoescape=True or use the " "select_autoescape function to mitigate XSS " "vulnerabilities.", ) # found autoescape if getattr(node, "arg", None) == "autoescape": value = getattr(node, "value", None) if ( getattr(value, "id", None) == "True" or getattr(value, "value", None) is True ): return # Check if select_autoescape function is used. elif ( isinstance(value, ast.Call) and getattr(value.func, "id", None) == "select_autoescape" ): return 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. " "Ensure autoescape=True or use the " "select_autoescape function to mitigate " "XSS vulnerabilities.", ) # We haven't found a keyword named autoescape, indicating default # 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 " "function to mitigate XSS vulnerabilities.", )
def start_process_with_a_shell(context, config): if config and context.call_function_name_qual in config['shell']: return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, text="Starting a process with a shell: check for injection." )
def dynamic_cmd_dispatch(context): call_node = context.node if not (isinstance(call_node.func, ast.Name) and call_node.func.id == 'getattr' and len(call_node.args) >= 2): return arg0, arg1 = call_node.args[:2] if not isinstance(arg0, ast.Name): return arg0 = context._context['import_aliases'].get(arg0.id, arg0.id) methods = SUBPROCESS.get(arg0) if methods is None: return confidence = bandit.LOW arg1_values = tuple( s_utils.iter_expr_literal_values(s_utils.get_top_parent_node( context.node), arg1, child=call_node)) if arg1_values: if all((name in methods for name in arg1_values)): confidence = bandit.HIGH elif any((name in methods for name in arg1_values)): confidence = bandit.MEDIUM else: return return bandit.Issue( severity=bandit.HIGH, confidence=confidence, text="Retrieved a function through which os commands can be executed.")
def insecure_hashlib_new_algorithm(context): call_node = context.node if not (context.call_function_name_qual == 'hashlib.new' and len(call_node.args)): return arg0 = call_node.args[0] parent = s_utils.get_top_parent_node(call_node) insecure_algorithms = ('md2', 'md4', 'md5') confidence = None if isinstance(arg0, ast.Str) and arg0.s.lower() in insecure_algorithms: confidence = bandit.HIGH elif isinstance(arg0, ast.Name): algorithms = tuple( s_utils.iter_expr_literal_values(parent, arg0, call_node)) if algorithms and all(algo.lower() in insecure_algorithms for algo in algorithms): confidence = bandit.HIGH elif algorithms and any(algo.lower() in insecure_algorithms for algo in algorithms): confidence = bandit.MEDIUM if confidence is None: return return bandit.Issue(severity=bandit.MEDIUM, confidence=confidence, text='Use of insecure MD2, MD4, or MD5 hash function.')
def snmp_insecure_version_check(context): """**B508: Checking for insecure SNMP versions** This test is for checking for the usage of insecure SNMP version like v1, v2c Using the pysnmp documentation: http://snmplabs.com/pysnmp/examples/hlapi/asyncore/sync/manager/cmdgen/snmp-versions.html Please update your code to use more secure versions of SNMP. .. versionadded:: 1.7.2 """ if context.call_function_name_qual == "CommunityData": # We called community data. Lets check our args if context.check_call_arg_value( "mpModel", 0 ) or context.check_call_arg_value("mpModel", 1): return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.HIGH, text="The use of SNMPv1 and SNMPv2 is insecure. " "You should use SNMPv3 if able.", lineno=context.get_lineno_for_call_arg("CommunityData"), )
def ssl_with_no_version(context): """Test for SSL use with no version specified This plugin is part of a family of tests that detect the use of known bad versions of SSL/TLS, please see :doc:`../plugins/ssl_with_bad_version` for a complete discussion. Specifically, This plugin test scans for specific methods in Python's native SSL/TLS support and the pyOpenSSL module that configure the version of SSL/TLS protocol to use. These methods are known to provide default value that maximize compatibility, but permit use of the aforementioned broken protocol versions. A LOW severity warning will be reported whenever this is detected. See also: - :doc:`../plugins/ssl_with_bad_version` - :doc:`../plugins/ssl_with_bad_defaults` Config Options: This test shares the configuration provided for the standard :doc:`../plugins/ssl_with_bad_version` test, please refer to its documentation. Sample Output: .. code-block:: none >> Issue: ssl.wrap_socket call with no SSL/TLS protocol version specified, the default SSLv23 could be insecure, possible security issue. Severity: Low Confidence: Medium Location: ./examples/ssl-insecure-version.py:23 22 23 ssl.wrap_socket() 24 References: - http://heartbleed.com/ - https://poodlebleed.com/ - https://security.openstack.org/ - https://security.openstack.org/guidelines/dg_move-data-securely.html .. versionadded:: 0.9.0 """ if (context.call_function_name_qual == 'ssl.wrap_socket'): if context.check_call_arg_value('ssl_version') is None: # check_call_arg_value() returns False if the argument is found # but does not match the supplied value (or the default None). # It returns None if the arg_name passed doesn't exist. This # tests for that (ssl_version is not specified). return bandit.Issue( severity=bandit.LOW, confidence=bandit.MEDIUM, text="ssl.wrap_socket call with no SSL/TLS protocol version " "specified, the default SSLv23 could be insecure, " "possible security issue.", lineno=context.get_lineno_for_call_arg('ssl_version'), )
def _report(value): return bandit.Issue( severity=bandit.LOW, confidence=bandit.MEDIUM, cwe=issue.Cwe.HARD_CODED_PASSWORD, text=("Possible hardcoded password: '******'" % value), )
def hardcoded_password(context, config): word_list_file = None word_list = [] # try to read the word list file from config if (config is not None and 'word_list' in config): try: word_list_file = find_word_list(config['word_list']) except RuntimeError as e: warnings.warn(e.message) return # try to open the word list file and read passwords from it try: f = open(word_list_file, 'r') except (OSError, IOError): raise RuntimeError("Could not open word_list (from config" " file): %s" % word_list_file) else: for word in f: word_list.append(word.strip()) f.close() # for every password in the list, check against the current string for word in word_list: if context.string_val and context.string_val == word: return bandit.Issue( severity=bandit.LOW, confidence=bandit.LOW, text="Possible hardcoded password '(%s)'" % word )
def hardcoded_bind_all_interfaces(context): if context.string_val == '0.0.0.0': return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, text="Possible binding to all interfaces." )
def key_generation_module_test(context): if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if 'key_generation' in qualname_list and func == 'key_generation_def': with open("./policy/policy.json", "r") as policy: policy_dict = json.load(policy) if policy_dict['policy']['identity_and_access']['identity'][ 'authenticator']['key']: key_generation_module = policy_dict['policy'][ 'identity_and_access']['identity']['authenticator']['key'][ 'key_generation_module'] args = context.call_args keywords = context.call_keywords #name = args[0] if args else keywords['name'] return bandit.Issue( severity=bandit.LOW, confidence=bandit.LOW, text= 'INFORMATIONAL: The approved modules for secure pseudorandom number generation are: ' + str(key_generation_module) + ' Please review to validate you are using one. If so, please consider this informational.', lineno=context.node.lineno)
def key_generation_length_test(context): if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if 'key_generation' in qualname_list and func == 'key_generation_def': with open("./policy/policy.json", "r") as policy: policy_dict = json.load(policy) if policy_dict['policy']['identity_and_access']['identity'][ 'authenticator']['key']: key_generation_length = policy_dict['policy'][ 'identity_and_access']['identity']['authenticator']['key'][ 'key_generation_length'] args = context.call_args keywords = context.call_keywords #name = args[0] if args else keywords['name'] return bandit.Issue( severity=bandit.LOW, confidence=bandit.LOW, text='INFORMATIONAL: Cryptographic keys must be at least ' + str(key_generation_length) + ' bytes to be considered secure. Please review to validate.', lineno=context.node.lineno)
def blacklist_calls(context, config): if config is not None and 'bad_name_sets' in config: sets = config['bad_name_sets'] else: sets = [] checks = [] # load all the checks from the config file for cur_item in sets: for blacklist_item in cur_item: blacklist_object = cur_item[blacklist_item] cur_check = _get_tuple_for_item(blacklist_object) # skip bogus checks if cur_check: checks.append(cur_check) # for each check, go through and see if it matches all qualifications for check in checks: confidence = 'HIGH' does_match = True # item 0=qualnames, 1=names, 2=message, 3=level, 4=params if does_match and check[0]: matched_qn = False for qn in check[0]: if context.call_function_name_qual == qn: matched_qn = True if not matched_qn: does_match = False if does_match and check[1]: matched_n = False for n in check[1]: if context.call_function_name == n: matched_n = True if not matched_n: does_match = False if does_match and check[4]: matched_p = False for p in check[4]: for arg_num in range(0, context.call_args_count - 1): if p == context.get_call_arg_at_position(arg_num): matched_p = True if not matched_p: does_match = False if does_match: level = None if check[3] == 'HIGH': level = bandit.HIGH elif check[3] == 'MEDIUM': level = bandit.MEDIUM elif check[3] == 'LOW': level = bandit.LOW return bandit.Issue( severity=level, confidence=confidence, text="%s %s" % (check[2], context.call_args_string) )
def _report(value): return bandit.Issue( severity=bandit.LOW, cwe=cwemap.CWEMAP["B105"], confidence=bandit.MEDIUM, text=("Possible hardcoded password: '******'" % value), )
def linux_commands_wildcard_injection(context, config): if not ("shell" in config and "subprocess" in config): return vulnerable_funcs = ["chown", "chmod", "tar", "rsync"] if context.call_function_name_qual in config["shell"] or ( context.call_function_name_qual in config["subprocess"] and context.check_call_arg_value("shell", "True")): if context.call_args_count >= 1: call_argument = context.get_call_arg_at_position(0) argument_string = "" if isinstance(call_argument, list): for li in call_argument: argument_string = argument_string + " %s" % li elif isinstance(call_argument, str): argument_string = call_argument if argument_string != "": for vulnerable_func in vulnerable_funcs: if (vulnerable_func in argument_string and "*" in argument_string): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.MEDIUM, text="Possible wildcard injection in call: %s" % context.call_function_name_qual, lineno=context.get_lineno_for_call_arg("shell"), )
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, confidence=bandit.MEDIUM, text="Starting a process without a shell." )
def linux_commands_wildcard_injection(context, config): if not ('shell' in config and 'subprocess' in config): return vulnerable_funcs = ['chown', 'chmod', 'tar', 'rsync'] if context.call_function_name_qual in config['shell'] or ( context.call_function_name_qual in config['subprocess'] and context.check_call_arg_value('shell', 'True')): if context.call_args_count >= 1: call_argument = context.get_call_arg_at_position(0) argument_string = '' if isinstance(call_argument, list): for li in call_argument: argument_string = argument_string + ' %s' % li elif isinstance(call_argument, str): argument_string = call_argument if argument_string != '': for vulnerable_func in vulnerable_funcs: if( vulnerable_func in argument_string and '*' in argument_string ): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.MEDIUM, text="Possible wildcard injection in call: %s" % context.call_function_name_qual, lineno=context.get_lineno_for_call_arg('shell'), )
def blacklist_calls(context, config): _ensure_cache(config) checks = _cached_blacklist_checks # for each check, go through and see if it matches all qualifications for qualnames, names, message_tpl, level, params in checks: confidence = 'HIGH' does_match = True # item 0=qualnames, 1=names, 2=message, 3=level, 4=params if does_match and qualnames: # match the qualname - respect wildcards if present does_match = any( fnmatch.fnmatch(context.call_function_name_qual, qn) for qn in qualnames) if does_match and names: does_match = any(context.call_function_name == n for n in names) if does_match and params: matched_p = False for p in params: for arg_num in range(0, context.call_args_count - 1): if p == context.get_call_arg_at_position(arg_num): matched_p = True if not matched_p: does_match = False if does_match: message = message_tpl.replace("{func}", context.call_function_name_qual) return bandit.Issue(severity=level, confidence=confidence, text=message, ident=context.call_function_name_qual)
def snmp_crypto_check(context): """**B509: Checking for weak cryptography** This test is for checking for the usage of insecure SNMP cryptography: v3 using noAuthNoPriv. Using the pysnmp documentation: http://snmplabs.com/pysnmp/examples/hlapi/asyncore/sync/manager/cmdgen/snmp-versions.html Please update your code to use more secure versions of SNMP. For example: Instead of: `CommunityData('public', mpModel=0)` Use (Defaults to usmHMACMD5AuthProtocol and usmDESPrivProtocol `UsmUserData("securityName", "authName", "privName")` .. versionadded:: 1.7.2 """ if context.call_function_name_qual == "UsmUserData": if context.call_args_count < 3: return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.HIGH, text="You should not use SNMPv3 without encryption. " "noAuthNoPriv & authNoPriv is insecure", lineno=context.get_lineno_for_call_arg("UsmUserData"), )
def exec_issue(): return bandit.Issue( severity=bandit.MEDIUM, cwe=cwemap.CWEMAP["B102"], confidence=bandit.HIGH, text="Use of exec detected.", )
def string_encode(context): #import pdb; pdb.set_trace() if isinstance(context.node._bandit_parent, ast.Attribute): if context.node._bandit_parent.attr == 'encode': return bandit.Issue(severity=bandit.MEDIUM, confidence=bandit.MEDIUM, text="string_encode")
def _classify_key_size(config, key_type, key_size): if isinstance(key_size, str): # size provided via a variable - can't process it at the moment return key_sizes = { "DSA": [ (config["weak_key_size_dsa_high"], bandit.HIGH), (config["weak_key_size_dsa_medium"], bandit.MEDIUM), ], "RSA": [ (config["weak_key_size_rsa_high"], bandit.HIGH), (config["weak_key_size_rsa_medium"], bandit.MEDIUM), ], "EC": [ (config["weak_key_size_ec_high"], bandit.HIGH), (config["weak_key_size_ec_medium"], bandit.MEDIUM), ], } for size, level in key_sizes[key_type]: if key_size < size: return bandit.Issue( severity=level, confidence=bandit.HIGH, cwe=issue.Cwe.INADEQUATE_ENCRYPTION_STRENGTH, text="%s key sizes below %d bits are considered breakable. " % (key_type, size), )
def exec_issue(): return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.HIGH, cwe=issue.Cwe.OS_COMMAND_INJECTION, text="Use of exec detected.", )
def set_bad_file_permissions(context): if "chmod" in context.call_function_name: if context.call_args_count == 2: mode = context.get_call_arg_at_position(1) if ( mode is not None and isinstance(mode, int) and _stat_is_dangerous(mode) ): # world writable is an HIGH, group executable is a MEDIUM if mode & stat.S_IWOTH: sev_level = bandit.HIGH else: sev_level = bandit.MEDIUM filename = context.get_call_arg_at_position(0) if filename is None: filename = "NOT PARSED" return bandit.Issue( severity=sev_level, confidence=bandit.HIGH, cwe=issue.Cwe.INCORRECT_PERMISSION_ASSIGNMENT, text="Chmod setting a permissive mask %s on file (%s)." % (oct(mode), filename), )
def secure_hash_test(context): if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if ('secure_hash' in qualname_list and func == 'secure_hash_def'): args = context.call_args keywords = context.call_keywords name = args[0] if args else keywords['name'] with open("./policy/policy.json", "r") as policy: policy_dict = json.load(policy) if policy_dict['policy']['data']['data_transformation'][ 'data_confidentiality']['hash']['secure_hash']: secure_hash_algorithm = policy_dict['policy']['data'][ 'data_transformation']['data_confidentiality']['hash'][ 'secure_hash'] if name.lower() not in secure_hash_algorithm: return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, text='Cryptographically weak hash algorithm detected. ' + name + ' is considered weak and should not be used for cryptographic purposes. ' + 'Please consult policy for acceptable secure hash algorithms.', lineno=context.node.lineno)
def assert_used(context): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH, text=("Use of assert detected. The enclosed code " "will be removed when compiling to optimised byte code.") )
def django_extra_used(context): """**B610: Potential SQL injection on extra function** .. seealso:: - https://docs.djangoproject.com/en/dev/topics/security/\ #sql-injection-protection .. versionadded:: 1.5.0 """ description = "Use of extra potential SQL attack vector." if context.call_function_name == "extra": kwargs = keywords2dict(context.node.keywords) args = context.node.args if args: if len(args) >= 1: kwargs["select"] = args[0] if len(args) >= 2: kwargs["where"] = args[1] if len(args) >= 3: kwargs["params"] = args[2] if len(args) >= 4: kwargs["tables"] = args[3] if len(args) >= 5: kwargs["order_by"] = args[4] if len(args) >= 6: kwargs["select_params"] = args[5] insecure = False for key in ["where", "tables"]: if key in kwargs: if isinstance(kwargs[key], ast.List): for val in kwargs[key].elts: if not isinstance(val, ast.Str): insecure = True break else: insecure = True break if not insecure and "select" in kwargs: if isinstance(kwargs["select"], ast.Dict): for k in kwargs["select"].keys: if not isinstance(k, ast.Str): insecure = True break if not insecure: for v in kwargs["select"].values: if not isinstance(v, ast.Str): insecure = True break else: insecure = True if insecure: return bandit.Issue( severity=bandit.MEDIUM, cwe=cwemap.CWEMAP["B611"], confidence=bandit.MEDIUM, text=description, )