def is_dynupdate_enabled(domain: str, nameserver: str) -> bool: """ Check if zone updating is enabled. :param domain: Name of the zone to transfer. :param nameserver: IPv4 or 6 to test. """ newrecord = 'newrecord' try: update = dns.update.Update(domain) update.add(newrecord, 3600, dns.rdatatype.A, '10.10.10.10') response = dns.query.tcp(update, nameserver, timeout=5) result = True if response.rcode() > 0: show_close('Zone update not enabled on server', details=dict(domain=domain, nameserver=nameserver)) result = False else: show_open('Zone update enabled on server', details=dict(domain=domain, nameserver=nameserver)) result = True except dns.query.BadResponse: show_close('Zone update not enabled on server', details=dict(domain=domain, nameserver=nameserver)) result = False except (socket.error, dns.exception.Timeout) as exc: show_unknown('Could not connect', details=dict(domain=domain, nameserver=nameserver, error=str(exc).replace(':', ','))) result = False return result
def has_generic_exceptions(rpg_dest: str, exclude: list = None) -> bool: """ Search for on-error empty. See `REQ. 161 <https://fluidattacks.com/web/rules/161/>`_. :param rpg_dest: Path to a RPG source or directory. :param exclude: Paths that contains any string from this list are ignored. """ tk_on = CaselessKeyword('on') tk_error = CaselessKeyword('error') tk_monitor = tk_on + Literal('-') + tk_error + Literal(';') result = False try: matches = lang.check_grammar(tk_monitor, rpg_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not have empty monitors', details=dict(code_dest=rpg_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=rpg_dest)) return False else: result = True show_open('Code has empty monitors', details=dict(matched=matches, total_vulns=len(matches))) return result
def has_unitialized_vars(rpg_dest: str, exclude: list = None) -> bool: """ Search for unitialized variables. :param rpg_dest: Path to a RPG source or directory. :param exclude: Paths that contains any string from this list are ignored. """ tk_data = Keyword('D') tk_first = Word(alphas + "_", exact=1) tk_rest = Word(alphanums + "_") tk_vartype = Word(alphas, exact=1) tk_varlen = Word(nums) + Word(alphas, exact=1) tk_inz = CaselessKeyword('inz') tk_varname = tk_first + tk_rest unitialized = tk_data + tk_varname + Optional(tk_vartype) + \ Optional(tk_varlen) + Optional(Word(nums)) + NotAny(tk_inz) result = False try: matches = lang.check_grammar(unitialized, rpg_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not have unitialized variables', details=dict(code_dest=rpg_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=rpg_dest)) return False else: result = True show_open('Code has unitialized variables', details=dict(matched=matches, total_vulns=len(matches))) return result
def uses_eval(js_dest: str, exclude: list = None) -> bool: """ Search for ``eval()`` calls in a JavaScript file or directory. :param js_dest: Path to a JavaScript source file or directory. :param exclude: Paths that contains any string from this list are ignored. """ method = 'eval()' tk_method = CaselessKeyword('eval') call_function = tk_method + Suppress(nestedExpr()) result = False try: matches = lang.check_grammar(call_function, js_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not use {} method'.format(method), details=dict(code_dest=js_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=js_dest)) return False else: result = True show_open('Code uses {} method'.format(method), details=dict(matched=matches, total_vulns=len(matches))) return result
def _ftp_do_auth(ip_address: str, username: str, password: str, port: int = PORT) -> bool: """ Perform FTP auth. :param ip_address: IP address to connect to. :param username: Username to check. :param password: Password to check. :param port: If necessary, specifiy port to connect to. """ result = False try: with FTP() as ftp: ftp.connect(ip_address, port) ftp.login(username, password) ftp.makepasv() result = True show_open('FTP Authentication {}:{}'.format(ip_address, port), details=dict(username=username, password=password)) except error_perm: show_close('FTP Authentication {}:{}'.format(ip_address, port), details=dict(username=username, password=password)) result = False except OSError as exc: show_unknown('Could not connect', details=dict(ip=ip_address, username=username, password=password, error=str(exc))) result = False return result
def uses_localstorage(js_dest: str, exclude: list = None) -> bool: """ Search for ``localStorage`` calls in a JavaScript source file or directory. :param js_dest: Path to a JavaScript source file or directory. :param exclude: Paths that contains any string from this list are ignored. """ method = 'window.localStorage' tk_object = CaselessKeyword('localstorage') tk_method = Word(alphanums) lsto = tk_object + Literal('.') + tk_method + Suppress(nestedExpr()) result = False try: matches = lang.check_grammar(lsto, js_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not use {} method'.format(method), details=dict(code_dest=js_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=js_dest)) return False else: result = True show_open('Code uses {} method'.format(method), details=dict(matched=matches, total_vulns=len(matches))) return result
def swallows_exceptions(js_dest: str, exclude: list = None) -> bool: """ Search for ``catch`` blocks that are empty or only have comments. See `REQ.161 <https://fluidattacks.com/web/rules/161/>`_. See `CWE-391 <https://cwe.mitre.org/data/definitions/391.html>`_. :param js_dest: Path to a JavaScript source file or package. :param exclude: Paths that contains any string from this list are ignored. """ # Empty() grammar matches 'anything' # ~Empty() grammar matches 'not anything' or 'nothing' classic = Suppress(Keyword('catch')) + nestedExpr(opener='(', closer=')') \ + nestedExpr(opener='{', closer='}', content=~Empty()) modern = Suppress('.' + Keyword('catch')) + nestedExpr( opener='(', closer=')', content=~Empty()) grammar = MatchFirst([classic, modern]) grammar.ignore(cppStyleComment) try: matches = lang.path_contains_grammar(grammar, js_dest, LANGUAGE_SPECS, exclude) except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=js_dest)) else: if matches: show_open('Code has empty "catch" blocks', details=dict(matched=matches)) return True show_close('Code does not have empty "catch" blocks', details=dict(code_dest=js_dest)) return False
def root_without_mfa(key_id: str, secret: str) -> bool: """ Check if root account does not have MFA. CIS 1.13: Ensure MFA is enabled for the root account (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret """ result = False try: summary = aws.get_account_summary(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False if summary['AccountMFAEnabled'] == 1: show_close('Root password has MFA enabled', details=dict(account_summary=summary)) result = False else: show_open('Root password has MFA disabled', details=dict(account_summary=summary)) result = True return result
def policies_attached_to_users(key_id: str, secret: str) -> bool: """ Check if there are policies attached to users. CIS 1.16: Ensure IAM policies are attached only to groups or roles (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret """ result = False try: users = aws.list_users(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False for user in users: user_pol = aws.list_attached_user_policies(key_id, secret, user['UserName']) if user_pol: show_open('User has policies directly attached', details=(dict(user=user['UserName'], user_policy=user_pol))) result = True else: show_close('User does not have policies attached', details=(dict(user=user['UserName']))) return result
def min_password_len_unsafe(key_id: str, secret: str, min_len=14) -> bool: """ Check if password policy requires passwords greater than 14 chars. CIS 1.9: Ensure IAM password policy requires minimum length of 14 or greater (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret :param min_len: Mininum length required. Default 14 """ result = False try: policy = aws.get_account_password_policy(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False if policy['MinimumPasswordLength'] >= min_len: show_close('Password policy requires long passwords', details=dict(min_length=min_len, policy=policy)) result = False else: show_open('Password policy does not require long passwords', details=dict(min_length=min_len, policy=policy)) result = True return result
def has_mfa_disabled(key_id: str, secret: str) -> bool: """ Search users with password enabled and without MFA. CIS 1.2: Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret """ result = False try: users = aws.get_credentials_report(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False for user in users: if user[3] == 'true': if user[7] == 'false': show_open('User has password enabled without MFA', details=dict(user=user[0])) result = True else: show_close('User has password enabled with MFA', details=dict(user=user[0])) else: show_close('User does not have password enabled', details=dict(user=user[0])) return result
def not_requires_numbers(key_id: str, secret: str) -> bool: """ Check if password policy requires numbers. CIS 1.8: Ensure IAM password policy require at least one number (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret """ result = False try: policy = aws.get_account_password_policy(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False if policy['RequireNumbers']: show_close('Password policy requires numbers', details=dict(policy=policy)) result = False else: show_open('Password policy does not require numbers', details=dict(policy=policy)) result = True return result
def root_has_access_keys(key_id: str, secret: str) -> bool: """ Check if root account has access keys. CIS 1.12: Ensure no root account access key exists (Scored) :param key_id: AWS Key Id :param secret: AWS Key Secret """ result = False try: users = aws.get_credentials_report(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False root_user = next(users) if root_user[8] == 'true' or root_user[13] == 'true': show_open('Root user has access keys', details=dict(user=root_user)) result = True else: show_close('Root user does not have access keys', details=dict(user=root_user)) result = False return result
def not_pinned(file_dest: str, exclude: list = None) -> bool: """ Check if the Dockerfile uses a ``FROM:...latest`` (unpinned) base image. :param file_dest: Path to the Dockerfile to be tested. :param exclude: Paths that contains any string from this list are ignored. :returns: True if unpinned (bad), False if pinned (good). """ tk_from = Literal('FROM') tk_image = Word(alphas) tk_version = Literal('latest') pinned = tk_from + tk_image + Literal(':') + tk_version result = False try: matches = lang.check_grammar(pinned, file_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Dockerfile has pinned base image(s)', details=dict(code_dest=file_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=file_dest)) return False else: result = True show_open('Dockerfile uses unpinned base image(s)', details=dict(file=matches, total_vulns=len(matches))) return result
def not_use_ssl(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if MySQL server uses SSL.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = 'SHOW variables WHERE variable_name = "have_ssl"' try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False _result = list(mycursor) result = _result[0][1] == 'DISABLED' if result: show_open('Server don\'t use SSL', details=dict(server=server)) else: show_close('Server uses SSL', details=dict(server=server)) return result
def is_anonymous_enabled(server: str, domain: str = 'WORKGROUP') -> bool: """ Check if anonymous login is possible over SMB. :param server: The NetBIOS machine name of the remote server. :param domain: The network domain/workgroup. Defaults to 'WORKGROUP' """ user = '******' password = '' try: with SMBConnection.SMBConnection(user, password, CLIENT_MACHINE_NAME, server, domain=domain, use_ntlm_v2=True, is_direct_tcp=True) as conn: ret = conn.connect(server, port=445) if not ret: show_close('Anonymous login not possible', details=dict(domain=domain, user=user, server=server)) return False show_open('Anonymous login enabled', details=dict(domain=domain, user=user, server=server)) return True except OSError as exc: show_unknown('There was an error connecting to SMB', details=dict(server=server, domain=domain, error=str(exc))) return False
def has_unencrypted_volumes(key_id: str, secret: str) -> bool: """ Check if there are unencrypted volumes. :param key_id: AWS Key Id :param secret: AWS Key Secret """ try: volumes = aws.list_volumes(key_id, secret) except aws.ConnError as exc: show_unknown('Could not connect', details=dict(error=str(exc).replace(':', ''))) return False except aws.ClientErr as exc: show_unknown('Error retrieving info. Check credentials.', details=dict(error=str(exc).replace(':', ''))) return False if not volumes: show_close('Not volumes found') return False result = False for volume in volumes: if not volume['Encrypted']: show_open('Volume is not encrypted', details=dict(volume=volume)) result = True else: show_close('Volume is encrypted', details=dict(volume=volume)) return result
def local_infile_enabled(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if 'local_infile' parameter is set to ON.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = "SHOW VARIABLES WHERE Variable_name = 'local_infile'" try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False result = ('local_infile', 'ON') in list(mycursor) if result: show_open('Parameter "local_infile" is ON on server', details=dict(server=server)) else: show_close('Parameter "local_infile" is OFF on server', details=dict(server=server)) return result
def has_insecure_randoms(js_dest: str, exclude: list = None) -> bool: r""" Check if code uses ``Math.Random()``\ . See `REQ.224 <https://fluidattacks.com/web/rules/224/>`_. :param js_dest: Path to a JavaScript source file or package. :param exclude: Paths that contains any string from this list are ignored. """ method = 'Math.random()' tk_class = CaselessKeyword('math') tk_method = CaselessKeyword('random') tk_params = nestedExpr() call_function = tk_class + Literal('.') + tk_method + Suppress(tk_params) result = False try: matches = lang.check_grammar(call_function, js_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not use {} method'.format(method), details=dict(code_dest=js_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=js_dest)) return False else: result = True show_open('Code uses {} method'.format(method), details=dict(matched=matches, total_vulns=len(matches))) return result
def symlinks_enabled(server: str, username: str, password: str, port: str = 3306) -> bool: """Check if symbolic links are enabled on MySQL server.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = "SHOW variables LIKE 'have_symlink'" try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False result = ('have_symlink', 'DISABLED') not in list(mycursor) if result: show_open('Symbolic links are supported by server', details=dict(server=server)) else: show_close('Symbolic links are not supported by server', details=dict(server=server)) return result
def has_switch_without_default(js_dest: str, exclude: list = None) -> bool: r""" Check if all ``switch``\ es have a ``default`` clause. See `REQ.161 <https://fluidattacks.com/web/rules/161/>`_. See `CWE-478 <https://cwe.mitre.org/data/definitions/478.html>`_. :param js_dest: Path to a JavaScript source file or package. :param exclude: Paths that contains any string from this list are ignored. """ switch = Keyword('switch') + nestedExpr(opener='(', closer=')') switch_block = Suppress(switch) + nestedExpr(opener='{', closer='}') switch_block.ignore(cppStyleComment) switch_block.ignore(L_CHAR) switch_block.ignore(L_STRING) switch_block.addCondition(lambda x: not RE_HAVES_DEFAULT.search(str(x))) try: matches = lang.path_contains_grammar(switch_block, js_dest, LANGUAGE_SPECS, exclude) except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=js_dest)) return False if matches: show_open('Code does not have "switch" with "default" clause', details=dict(matched=matches)) return True show_close('Code has "switch" with "default" clause', details=dict(code_dest=js_dest)) return False
def strict_all_tables_disabled(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if STRICT_ALL_TABLES is enabled on MySQL server.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = "SHOW VARIABLES LIKE 'sql_mode'" try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False result = 'STRICT_ALL_TABLES' not in list(mycursor)[0][1] if result: show_open('STRICT_ALL_TABLES not enabled on by server', details=dict(server=server)) else: show_close('STRICT_ALL_TABLES enabled on by server', details=dict(server=server)) return result
def has_not_autocomplete(filename: str) -> bool: """ Check the autocomplete attribute. Check if tags ``form`` and ``input`` have the ``autocomplete`` attribute set to ``off``. :param filename: Path to the ``HTML`` source. :returns: True if tags ``form`` and ``input`` have attribute ``autocomplete`` set as specified, False otherwise. """ tk_off = CaselessKeyword('off') attr = {'autocomplete': tk_off} tag_i = 'input' tag_f = 'form' try: has_input = _has_attributes(filename, tag_i, attr) has_form = _has_attributes(filename, tag_f, attr) except FileNotFoundError as exc: show_unknown('There was an error', details=dict(error=str(exc))) return False if not (has_input or has_form): result = True show_open('Attribute in {}'.format(filename), details=dict(atributes=str(attr))) else: result = False show_close('Attribute in {}'.format(filename), details=dict(atributes=str(attr))) return result
def log_error_disabled(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if 'log_error' parameter is set on MySQL server.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = "SHOW variables LIKE 'log_error'" try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False result = ('log_error', '') in list(mycursor) if result: show_open('Parameter "log_error" not set on server', details=dict(server=server)) else: show_close('Parameter "log_error" is set on server', details=dict(server=server)) return result
def has_no_password_protection(path: str) -> bool: """ Check if .jks files are password protected. :param path: path to check """ if not os.path.exists(path): show_unknown('Path does not exist', details=dict(path=path)) return False jks_with_password: list = [] jks_without_password: list = [] for full_path in full_paths_in_dir(path): if not full_path.endswith('.jks'): continue try: jks.KeyStore.load(full_path, '') except jks.util.KeystoreSignatureException: # has password jks_with_password.append(dict(path=full_path, sha256=get_sha256(full_path))) else: # has not password jks_without_password.append(dict(path=full_path, sha256=get_sha256(full_path))) if jks_without_password: show_open('JKS is/are not password protected', details=dict(jks_without_password=jks_without_password)) return True show_close('JKS is/are password protected', details=dict(jks_with_password=jks_with_password)) return False
def logs_on_system_fs(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if logs are stored on a system filesystem on server.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() query = "SELECT @@global.log_bin_basename" try: mycursor.execute(query) except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False _result = list(mycursor)[0][0] result = _result.startswith('/var') or _result.startswith('/usr') if result: show_open('Logs are stored on system filesystems on server', details=dict(server=server)) else: show_close('Logs are outside system filesystems on server', details=dict(server=server)) return result
def has_dos_dow_sqlcod(rpg_dest: str, exclude: list = None) -> bool: r""" Search for DoS for using ``DoW SQLCOD = <ZERO>``\ . :param rpg_dest: Path to a RPG source or directory. :param exclude: Paths that contains any string from this list are ignored. """ tk_dow = CaselessKeyword('dow') tk_sqlcod = CaselessKeyword('sqlcod') tk_literal_zero = CaselessKeyword('*zeros') tk_zeros = MatchFirst([Literal('0'), tk_literal_zero]) dos_dow_sqlcod = tk_dow + tk_sqlcod + Literal('=') + tk_zeros result = False try: matches = lang.check_grammar(dos_dow_sqlcod, rpg_dest, LANGUAGE_SPECS, exclude) if not matches: show_close('Code does not have DoS for using "DoW SQLCOD = 0"', details=dict(code_dest=rpg_dest)) return False except FileNotFoundError: show_unknown('File does not exist', details=dict(code_dest=rpg_dest)) return False else: result = True show_open('Code has DoS for using "DoW SQLCOD = 0"', details=dict(matched=matches, total_vulns=len(matches))) return result
def test_db_exists(server: str, username: str, password: str, port: int = 3306) -> bool: """Check if "test" database exists.""" try: mydb = _get_mysql_cursor(server, username, password, port) except ConnError as exc: show_unknown('There was an error connecting to MySQL engine', details=dict(server=server, user=username, error=str(exc))) return False else: mycursor = mydb.cursor() try: mycursor.execute("SHOW DATABASES") except mysql.connector.errors.ProgrammingError as exc: show_unknown('There was an error executing query', details=dict(server=server, username=username, error=str(exc).replace(':', ','))) return False result = ('test', ) in list(mycursor) if result: show_open('Database "test" is present', details=dict(server=server)) else: show_close('Database "test" not present', details=dict(server=server)) return result
def has_insecure_expiration_time(jwt_token: str, max_expiration_time: int = 600) -> bool: """ Check if the given JWT has an insecure expiration time. :param jwt_token: JWT to test. :param max_expiration_time: According to the bussiness rule, (in seconds). """ try: claimset = decode(jwt_token, verify=False) except InvalidTokenError: show_unknown('Unable to decode token.', details=dict(jwt_token=jwt_token)) return False iat = claimset.get('iat') exp = claimset.get('exp') if not iat or not exp: show_open('Token does not include an `iat` or `exp` claims', details=dict(claims_set=claimset)) return True expiration_time = (exp - iat) if expiration_time > max_expiration_time: show_open('Token has an insecure expiration time', details=dict(claims_set=claimset, expiration_time=expiration_time, max_expiration_time=max_expiration_time)) else: show_close('Token has a secure expiration time', details=dict(claims_set=claimset, expiration_time=expiration_time, max_expiration_time=max_expiration_time)) return expiration_time > max_expiration_time
def has_recursion(nameserver: str) -> bool: """ Check if nameserver has recursion enabled. :param nameserver: IPv4 or 6 to test. """ domain = 'google.com.' name = dns.name.from_text(domain) try: # Make a recursive request request = dns.message.make_query(name, dns.rdatatype.A, dns.rdataclass.IN) response = dns.query.udp(request, nameserver, timeout=5) result = True if response.rcode() == 0: show_open('Recursion possible on server', details=dict(domain=domain, nameserver=nameserver)) result = True else: show_close('Recursion not possible on server', details=dict(domain=domain, nameserver=nameserver)) result = False except dns.exception.SyntaxError: show_close('Recursion not possible on server', details=dict(domain=domain, nameserver=nameserver)) result = False except (socket.error, dns.exception.Timeout) as exc: show_unknown('Could not connect', details=dict(domain=domain, nameserver=nameserver, error=str(exc).replace(':', ','))) result = False return result