def has_tls13_downgrade_vuln(site: str, port: int = PORT) -> bool: """ Check if server is prone to TLSv1.3 downgrade attack. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ supported = [] for version in reversed(range(0, 5)): try: with connect(site, port=port, min_version=(3, version), max_version=(3, version)): supported.append(version) except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSLocalAlert, OSError): continue if not supported: show_unknown('Could not connect to server', details=dict(site=site, port=port)) return False if 4 not in supported: show_unknown('Site does not support TLSv1.3', details=dict(site=site, port=port)) return False if len(supported) > 1: try: with connect(site, port=port, min_version=(3, min(supported)), max_version=(3, min(supported)), key_exchange_names=['rsa']): show_open('Site supports TLSv1.3 and older versions and \ supports RSA keys without (EC)DH(E) cipher suites', details=dict(site=site, port=port, supported=supported)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('Site supports TLSv1.3 older versions but \ RSA keys require (EC)DH(E) cipher suites', details=dict(site=site, port=port, supported=supported)) result = False except (tlslite.errors.TLSLocalAlert, socket.error) as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) result = False else: show_close('Site not vulnerable to TLSv1.3 downgrade attack', details=dict(site=site, port=port, supported=supported)) result = False return result
def allows_insecure_downgrade(site: str, port: int = PORT) -> bool: """ Check if site has support for TLS_FALLBACK_SCSV extension. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ supported = [] for version in reversed(range(0, 5)): try: with connect(site, port=port, max_version=(3, version)): supported.append(version) except (tlslite.errors.TLSRemoteAlert, OSError): continue except tlslite.errors.TLSLocalAlert: show_unknown('Port does not support SSL/TLS', details=dict(site=site, port=port)) return False if not supported: show_unknown('Could not connect to server', details=dict(site=site, port=port)) return False result = True if len(supported) > 1 and any(x in (0, 1, 2) for x in supported): try: with connect(site, port=port, max_version=(3, min(supported)), scsv=True): show_open('Site does not support TLS_FALLBACK_SCSV', details=dict(site=site, port=port)) result = True except tlslite.errors.TLSRemoteAlert as exc: if str(exc) in ('inappropriate_fallback', 'close_notify'): show_close('Site supports TLS_FALLBACK_SCSV', details=dict(site=site, port=port)) else: show_unknown('Could not connect to server', details=dict(site=site, port=port, error=str(exc).replace(':', ','))) result = False else: show_close('Host does not support multiple TLS versions', details=dict(site=site, port=port)) result = False return result
def has_sweet32(site: str, port: int = PORT) -> bool: """ Check if server is vulnerable to SWEET32. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = False try: with connect(site, port=port, cipher_names=["3des"], min_version=(3, 1), max_version=(3, 3)): show_open('Site vulnerable to SWEET32', details=dict(site=site, port=port)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError, socket.error): result = False show_close('Site not vulnerable to SWEET32', details=dict(site=site, port=port)) except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False return result
def tls_uses_cbc(site: str, port: int = PORT) -> bool: """ Check if TLS connection uses CBC. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = False try: with connect(site, port=port, min_version=(3, 1), max_version=(3, 3)) as conn: if conn._recordLayer.isCBCMode(): # noqa show_open('Site uses TLS CBC ciphers and may be vulnerable to \ to GOLDENDOODLE and Zombie POODLE attacks', details=dict(site=site, port=port)) result = True else: show_close('Site does not use TLS CBC ciphers', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError, socket.error) as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False return result
def not_tls13_enabled(site: str, port: int = PORT) -> bool: """ Check if site has TLSv1.3 enabled. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = True try: with connect(site, port=port, min_version=(3, 4), max_version=(3, 4)): show_close('Site supports TLSv1.3', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert) as exc: if exc.message and 'Too old version' in exc.message: show_open('Site does not support TLSv1.3', details=dict(site=site, port=port)) return True show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) return False except socket.error as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return result
def allows_weak_ciphers(site: str, port: int = PORT) -> bool: """ Check if site accepts weak cipher suites. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = True try: with connect(site, port=port, cipher_names=['rc4', '3des', 'null']): show_open('Site allows weak (RC4, 3DES and NULL) cipher \ suites', details=dict(site=site, port=port)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('Site not allows weak (RC4, 3DES and NULL) cipher \ suites', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False except socket.error as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return result
def has_beast(site: str, port: int = PORT) -> bool: """ Check if site allows BEAST attack. See our `blog entry on BEAST <https://fluidattacks.com/web/blog/release-the-beast/>`_. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = True try: with connect(site, port=port, min_version=(3, 1), max_version=(3, 1)) as conn: if conn._recordLayer.isCBCMode(): # noqa show_open('Site enables BEAST attack to clients', details=dict(site=site, port=port)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('Site not enables BEAST attack to clients', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False except socket.error as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) result = False return result
def has_poodle_sslv3(site: str, port: int = PORT) -> bool: """ Check if POODLE SSLv3 is present. See our `blog entry on POODLE <https://fluidattacks.com/web/blog/treacherous-poodle/>`_. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = False try: with connect(site, port=port, min_version=(3, 0), cipher_names=["aes256", "aes128", "3des"], max_version=(3, 0), check='POODLE'): show_open('Site vulnerable to POODLE SSLv3 attack', details=dict(site=site, port=port)) return True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('Site not vulnerable to POODLE SSLv3 attack', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False except socket.error as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return result
def allows_anon_ciphers(site: str, port: int = PORT) -> bool: """ Check if site accepts anonymous cipher suites. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = True try: with connect(site, port=port, anon=True): show_open('Site allows anonymous cipher suites', details=dict(site=site, port=port)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('Site not allows anonymous cipher suites', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False except socket.error as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return result
def is_pfs_disabled(site: str, port: int = PORT) -> bool: """ Check if PFS is enabled. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ try: with connect(site, port=port, key_exchange_names=[ 'dhe_rsa', 'ecdhe_rsa', 'ecdh_anon', 'dh_anon' ]): show_close('Forward Secrecy enabled on site', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_open('Forward Secrecy not enabled on site', details=dict(site=site, port=port)) return True except (tlslite.errors.TLSLocalAlert, socket.error) as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) result = False return result
def is_tlsv11_enabled(site: str, port: int = PORT) -> bool: """ Check if TLSv1.1 suites are enabled. :param site: Address to connect to. :param port: If necessary, specify port to connect to. """ result = True try: with connect(site, port=port, min_version=(3, 2), max_version=(3, 2)): show_open('TLSv1.1 enabled on site', details=dict(site=site, port=port)) result = True except (tlslite.errors.TLSRemoteAlert, tlslite.errors.TLSAbruptCloseError): show_close('TLSv1.1 not enabled on site', details=dict(site=site, port=port)) result = False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) result = False except socket.error as exc: result = False show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc))) return result
def is_cert_validity_lifespan_unsafe(site: str, port: int = PORT) -> bool: """ Check if certificate lifespan is larger than two years which is insecure. :param site: Site address. :param port: Port to connect to. """ max_validity_days = 730 result = True try: with connect(site, port=port) as conn: __cert = conn.session.serverCertChain.x509List[0].bytes cert = ssl.DER_cert_to_PEM_cert(__cert) except socket.error: show_unknown('Port closed', details=dict(site=site, port=port)) return False except tlslite.errors.TLSRemoteAlert as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc).replace(':', ','))) return False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) return False cert_obj = load_pem_x509_certificate(cert.encode('utf-8'), default_backend()) cert_validity = \ cert_obj.not_valid_after - cert_obj.not_valid_before if cert_validity.days <= max_validity_days: show_close('Certificate has a secure lifespan', details=dict( site=site, port=port, not_valid_before=cert_obj.not_valid_before.isoformat(), not_valid_after=cert_obj.not_valid_after.isoformat(), max_validity_days=max_validity_days, cert_validity_days=cert_validity.days)) result = False else: show_open('Certificate has an insecure lifespan', details=dict( site=site, port=port, not_valid_before=cert_obj.not_valid_before.isoformat(), not_valid_after=cert_obj.not_valid_after.isoformat(), max_validity_days=max_validity_days, cert_validity_days=cert_validity.days)) result = True return result
def is_cert_cn_not_equal_to_site(site: str, port: int = PORT) -> bool: """ Check if certificate Common Name (CN) is different from given sitename. Name in certificate should be coherent with organization name, see `REQ. 093 <https://fluidattacks.com/web/rules/093/>`_ :param site: Site address. :param port: Port to connect to. """ result = True try: with connect(site, port=port) as conn: __cert = conn.session.serverCertChain.x509List[0].bytes cert = ssl.DER_cert_to_PEM_cert(__cert) except socket.error: show_unknown('Port closed', details=dict(site=site, port=port)) return False except tlslite.errors.TLSRemoteAlert as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc).replace(':', ','))) return False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) return False cert_obj = load_pem_x509_certificate(cert.encode('utf-8'), default_backend()) cert_cn = \ cert_obj.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[ 0].value.lower() wc_cert = '*.' + site.lower() domain = 'NONE' if cert_cn.startswith('*.'): domain = '.' + cert_cn.split('*.')[1].lower() if (site.lower() != cert_cn and wc_cert != cert_cn and not site.endswith(domain)): show_open('{} CN not equals to site'.format(cert_cn), details=dict(site=site, port=port, cn=cert_cn)) result = True else: show_close('{} CN equals to site'.format(cert_cn), details=dict(site=site, port=port, cn=cert_cn)) result = False return result
def is_cert_inactive(site: str, port: int = PORT) -> bool: """ Check if certificate is no longer valid. Fails if end of validity date obtained from certificate is beyond the time of execution. :param site: Site address. :param port: Port to connect to. """ result = True try: with connect(site, port=port) as conn: __cert = conn.session.serverCertChain.x509List[0].bytes cert = ssl.DER_cert_to_PEM_cert(__cert) except socket.error: show_unknown('Port closed', details=dict(site=site, port=port)) return False except tlslite.errors.TLSRemoteAlert as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc).replace(':', ','))) return False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) return False cert_obj = load_pem_x509_certificate(cert.encode('utf-8'), default_backend()) if cert_obj.not_valid_after > datetime.datetime.now(): show_close('Certificate is still valid', details=dict( site=site, port=port, not_valid_after=cert_obj.not_valid_after.isoformat(), current_time=datetime.datetime.now().isoformat())) result = False else: show_open('Certificate is expired', details=dict( site=site, port=port, not_valid_after=cert_obj.not_valid_after.isoformat(), current_time=datetime.datetime.now().isoformat())) result = True return result
def _uses_sign_alg(site: str, alg: str, port: int) -> bool: """ Check if the given hashing method was used in signing the site certificate. :param site: Address to connect to. :param alg: Hashing method to test. :param port: Port to connect to. """ result = True try: with connect(site, port=port) as connection: __cert = connection.session.serverCertChain.x509List[0].bytes cert = ssl.DER_cert_to_PEM_cert(__cert) except socket.error: show_unknown('Port closed', details=dict(site=site, port=port)) return False except tlslite.errors.TLSRemoteAlert as exc: show_unknown('Could not connect', details=dict(site=site, port=port, error=str(exc).replace(':', ','))) return False except (tlslite.errors.TLSLocalAlert): show_unknown('Port doesn\'t support SSL', details=dict(site=site, port=port)) return False cert_obj = load_pem_x509_certificate(cert.encode('utf-8'), default_backend()) sign_algorith = cert_obj.signature_hash_algorithm.name if alg in sign_algorith: show_open('Certificate has {} as signature algorithm'.format( sign_algorith.upper()), details=dict(site=site, port=port)) result = True else: show_close('Certificate does not use {} as signature algorithm'.format( alg.upper()), details=dict(site=site, port=port, cert_algorithm=sign_algorith.upper())) result = False return result
def is_port_insecure(ipaddress: str, port: int) -> bool: """ Check if a given port on an IP address is insecure. :param ipaddress: IP address to test. :param port: Port to connect to. """ try: with ssl_helper.connect(ipaddress, port): show_close('Port is secure', details=dict(ip=ipaddress, port=port)) return False except (ConnectionRefusedError, socket.timeout): show_unknown('Could not connect', details=dict(ip=ipaddress, port=port)) return False except (tlslite.errors.TLSIllegalParameterException, tlslite.errors.TLSLocalAlert): show_open('Port is not secure', details=dict(ip=ipaddress, port=port)) return True except OverflowError: show_unknown('Bad arguments were given', details=dict(ip=ipaddress, port=port)) return False