def _get_simulations(ep, protos): if ep["details"]["sims"]["results"] is not None: output.norm("\tHandshake Simulation:") for sim in ep["details"]["sims"]["results"]: name = f'{sim["client"]["name"]} {sim["client"]["version"]}' if "platform" in sim["client"]: name += f' / {sim["client"]["platform"]}' name = name.ljust(28) if sim["errorCode"] == 0: protocol = protos[sim["protocolId"]] ke = _get_key_exchange(sim, True) if ke is not None: suite_name = f'{sim["suiteName"]} - {ke}' else: suite_name = f'{sim["suiteName"]}' output.norm(f"\t\t{name} - {protocol} - {suite_name}") else: output.info(f"\t\t{name} - Simulation Failed") output.empty()
def _shutdown(): global _start_time, _monitor, _has_shutdown if _has_shutdown: return _has_shutdown = True output.debug("Shutting down...") elapsed = datetime.now() - _start_time mem_res = "{0:cM}".format(Size(_monitor.peak_mem_res)) reporter.register_info("peak_memory", _monitor.peak_mem_res) output.empty() if _monitor.peak_mem_res > 0: output.norm( f"Completed (Elapsed: {str(elapsed)} - Peak Memory: {mem_res})") else: # if we don't have memory info - likely not running in a terminal, don't print junk output.norm(f"Completed (Elapsed: {str(elapsed)})") if reporter.get_output_file() != "": with Spinner() as spinner: reporter.save_output(spinner)
def main(): global _start_time, _monitor signal.signal(signal.SIGINT, signal_handler) warnings.simplefilter("ignore") try: if str(sys.stdout.encoding).lower() != "utf-8": print( f"Output encoding is {sys.stdout.encoding}: changing to UTF-8") sys.stdout.reconfigure(encoding="utf-8") except Exception as error: print(f"Unable to set UTF-8 encoding: {str(error)}") parser = command_line.build_parser() args, urls = parser.parse_known_args() # setup the output system output.setup(args.debug, args.nocolors, args.nowrap) output.debug("Starting application...") proxy = args.proxy if "proxy" in args else None cookie = args.cookie if "cookie" in args else None header = args.header if "header" in args else None network.init(proxy, cookie, header) # if we made it this far, it means that the parsing worked. # version doesn't require any URLs, so it gets special handing if args.command != "version": urls = command_line.process_urls(urls) else: urls = [] # we are good to keep going print_header() if args.output is not None: reporter.init(args.output) _set_basic_info() print(f"Saving output to '{reporter.get_output_file()}'") print() try: with _KeyMonitor(): with _ProcessMonitor() as pm: _monitor = pm args.func(args, urls) except KeyboardInterrupt: output.empty() output.error("Scan cancelled by user.") finally: _shutdown()
def _check_open_ports(domain: str, url: str, file: Optional[str] = None): try: output.empty() output.norm("Open Ports:") ips = basic.get_ips(domain) for ip in ips: with Spinner(): res = port_scan.check_open_ports(url, ip, file) if len(res) > 0: reporter.display_results(res, "\t") except Exception as error: output.error(f"Error checking for open ports: {str(error)}")
def signal_handler(sig, frame): if sig == signal.SIGINT: # check to see if we are a worker, or the main process if current_process().name == "MainProcess": output.empty() output.norm("Scan cancelled by user.") _shutdown() try: active_children() except: # we don't care if this fails pass sys.exit(1)
def _shutdown(): global _start_time, _monitor, _has_shutdown if _has_shutdown: return _has_shutdown = True output.debug("Shutting down...") elapsed = datetime.now() - _start_time mem_res = "{0:cM}".format(Size(_monitor.peak_mem_res)) output.empty() output.norm( f"Completed (Elapsed: {str(elapsed)} - Peak Memory: {mem_res})") if reporter.get_output_file() != "": with Spinner(): reporter.save_output()
def _get_cert_chain(chain: List[x509.Certificate], url: str): if len(chain) > 0: output.norm("\tCertificate Chain:") for cert in chain: output.norm(f"\t\tSubject: {cert.subject.rfc4514_string()}") output.norm( f"\t\t Signature: {cert.signature_algorithm_oid._name}") fp = bytes.hex(cert.fingerprint(hashes.SHA256())) if cert_info.check_symantec_root(fp): reporter.display( "\t\t Untrusted Symantec Root", issue.Issue(Vulnerabilities.TLS_SYMANTEC_ROOT, url, {"fingerprint": fp}), ) output.norm( f"\t\t https://crt.sh/?q={bytes.hex(cert.fingerprint(hashes.SHA1()))}" ) output.empty()
def main(): global _start_time, _monitor signal.signal(signal.SIGINT, signal_handler) parser = command_line.build_parser() args, urls = parser.parse_known_args() # setup the output system output.setup(args.debug, args.nocolors) output.debug("Starting application...") network.init(args.proxy, args.cookie) # if we made it this far, it means that the parsing worked. urls = command_line.process_urls(urls) # we are good to keep going print_header() if args.output is not None: reporter.init(args.output) _set_basic_info() print(f"Saving output to '{reporter.get_output_file()}'") print() try: with _KeyMonitor(): with _ProcessMonitor() as pm: _monitor = pm args.func(args, urls) except KeyboardInterrupt: output.empty() output.error("Scan cancelled by user.") finally: _shutdown()
def _get_leaf_cert_info(cert: x509.Certificate): output.norm("Certificate Information:") output.norm(f"\tSubject: {cert.subject.rfc4514_string()}") output.norm( f'\tCommon Names: {" ".join(cert_info.get_common_names(cert))}') output.norm("\tAlternative names:") alt_names = cert_info.get_alt_names(cert) for name in alt_names: output.norm(f"\t\t{name}") output.norm(f'\tNot Before: {cert.not_valid_before.isoformat(" ")}') output.norm(f'\tNot After: {cert.not_valid_after.isoformat(" ")}') output.norm(f"\tKey: {cert.signature_algorithm_oid._name}") # TODO: Public Key Hash serial = format(cert.serial_number, "02x") output.norm(f"\tSerial: {serial}") output.norm(f"\tIssuer: {cert.issuer.rfc4514_string()}") output.norm(f"\tOCSP Must Staple: {cert_info.get_must_staple(cert)}") output.empty() exts = cert_info.format_extensions(cert) for ext in exts: output.norm(f"\tExtensions: {ext}") output.empty() scts = cert_info.get_scts(cert) for sct in scts: output.norm( f'\tSCT: {cert_info.get_ct_log_name(sct[1])} - {sct[2].isoformat(" ")}' ) output.empty() cert_hash = bytes.hex(cert.fingerprint(hashes.SHA1())) output.norm(f"\tFingerprint: {cert_hash}") output.norm(f"\t\thttps://censys.io/certificates?q={cert_hash}") output.norm(f"\t\thttps://crt.sh/?q={cert_hash}") output.empty()
def _get_protocol_info(ep, url): output.norm("Configuration Information:") output.norm("\tProtocol Support:") # check protocols protos = {} tls13_enabled = False for proto in ep["details"]["protocols"]: if proto["name"] == "SSL": # show a vuln for SSLvX reporter.display( f'\t\t{proto["name"]} {proto["version"]}', issue.Issue(Vulnerabilities.TLS_LEGACY_SSL_ENABLED, url, {"ip": ep["ipAddress"]}), ) elif proto["name"] == "TLS" and proto["version"] == "1.0": # show a warn for TLSv1.0 reporter.display( f'\t\t{proto["name"]} {proto["version"]}', issue.Issue( Vulnerabilities.TLS_VERSION_1_0_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) elif proto["name"] == "TLS" and proto["version"] == "1.3": # capture TLS 1.3 status tls13_enabled = True output.norm(f'\t\t{proto["name"]} {proto["version"]}') else: output.norm(f'\t\t{proto["name"]} {proto["version"]}') protos[proto["id"]] = f'{proto["name"]} {proto["version"]}' if not tls13_enabled: reporter.display( "\t\tTLS 1.3 Is Not Enabled", issue.Issue( Vulnerabilities.TLS_VERSION_1_3_NOT_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) output.empty() if "namedGroups" in ep["details"]: output.norm("\tNamed Group Support:") for group in ep["details"]["namedGroups"]["list"]: output.norm(f'\t\t{group["name"]} {group["bits"]}') output.empty() if "suites" in ep["details"]: output.norm("\tCipher Suite Support:") for proto_suites in ep["details"]["suites"]: output.norm(f'\t\t{protos[proto_suites["protocol"]]}') for suite in proto_suites["list"]: ke = _get_key_exchange(suite) strength = suite["cipherStrength"] if "3DES" in suite["name"]: # in this case, the effective strength is only 112 bits, # which is what we want to report. So override SSL Labs strength = 112 if ke is not None: suite_info = f'{suite["name"].ljust(50)} - {strength}-bits - {ke}' else: suite_info = f'{suite["name"].ljust(50)} - {strength}-bits' if _is_cipher_suite_secure(suite): if "CBC" in suite["name"]: output.info(f"\t\t {suite_info}") reporter.register( issue.Issue( Vulnerabilities.TLS_CBC_CIPHER_SUITE, url, {"cipher": suite_info}, )) else: output.norm(f"\t\t {suite_info}") else: output.vuln(f"\t\t {suite_info}") reporter.register( issue.Issue( Vulnerabilities.TLS_INSECURE_CIPHER_SUITE, url, {"cipher": suite_info}, )) output.empty() _get_simulations(ep, protos)
def scan(session: Session): tty = sys.stdout.isatty() output.norm("Beginning SSL Labs scan (this could take a minute or two)") output.empty() # list the messages from SSL Labs - this is required as part of the ToS messages = api.get_info_message() for msg in messages: output.norm("[SSL Labs] {msg}".format(msg=msg)) api.start_scan(session.domain) status = "" error_count = 0 completed: List[str] = [] body = None while status != "READY" and status != "ERROR": sleep(5) try: status, body = api.check_scan(session.domain) except Exception: # if we find ourselves here, we want to try a couple more times before we give up for good output.debug_exception() if error_count > 3: raise else: error_count += 1 if tty: # clear the current line sys.stdout.write("\r\033[K") # display the current status if "endpoints" in body: msg = "" for ep in body["endpoints"]: if (ep["statusMessage"] == "Ready" and ep["ipAddress"] not in completed): completed.append(ep["ipAddress"]) sys.stdout.write( f'\r Status - {ep["ipAddress"]}: {ep["statusMessage"]}\n\r' ) elif (ep["statusMessage"] != "Pending" and ep["statusMessage"] != "Ready"): # get the completion percentage pct = "--" if "progress" in ep: pct = ep["progress"] sm = "unknown" if "statusDetailsMessage" in ep: sm = ep["statusDetailsMessage"] msg = f'\r Status - {ep["ipAddress"]}: {ep["statusMessage"]} ({sm}) - {pct}%' break if msg != "": sys.stdout.write(msg) else: sys.stdout.write(f"\r Status: Working...") elif "status" in body: sys.stdout.write(f'\r Status: {body["status"]}') else: sys.stdout.write(f"\r Status: Working...") # flush the buffer, to make sure it's actually written sys.stdout.flush() else: print(".", end="", flush=True) output.empty() output.empty() reporter.register_data("ssl_labs_results", body) # HACK: this needs to be refactored, once we have a better way to do it. This is awful. # (from a separation of duties perspective. this should happen in the plugin) if body["status"] == "ERROR": raise ValueError( f"SSL Labs Error: {body['status']}: {body['statusMessage']}") elif "endpoints" in body: for ep in body["endpoints"]: if ep["statusMessage"] == "Ready": output.norm(f'IP: {ep["ipAddress"]} - Grade: {ep["grade"]}') _get_cert_info(body, ep, session.url) _get_protocol_info(ep, session.url) _get_vulnerability_info(ep, session.url) else: output.error( f'Error getting information for IP: {ep["ipAddress"]}: {ep["statusMessage"]}' ) output.empty() else: output.debug(f"Invalid response received: {body}") output.error("Invalid response received: Endpoint data not found.") output.empty() raise ValueError(f"SSL Labs Error: {body['status']}")
def _get_cert_info(body, ep, url): # get the ChainCert info for the server cert - needed for extra details cert = None x509_cert = None for c in body["certs"]: if c["id"] == ep["details"]["certChains"][0]["certIds"][0]: cert = c x509_cert = x509.load_pem_x509_certificate( bytes(cert["raw"], "utf_8"), default_backend()) output.norm("Certificate Information:") if cert["issues"] > 0: output.warn("\tCertificate Has Issues - Not Valid") if cert["issues"] & 1 != 0: reporter.display( "\tCertificate Issue: no chain of trust", issue.Issue(Vulnerabilities.TLS_CERT_NO_TRUST, url, {"cert": cert}), ) if cert["issues"] & (1 << 1) != 0: reporter.display( "\tCertificate Issue: certificate not yet valid", issue.Issue(Vulnerabilities.TLS_CERT_NOT_YET_VALID, url, {"cert": cert}), ) if cert["issues"] & (1 << 2) != 0: reporter.display( "\tCertificate Issue: certificate expired", issue.Issue(Vulnerabilities.TLS_CERT_EXPIRED, url, {"cert": cert}), ) if cert["issues"] & (1 << 3) != 0: reporter.display( "\tCertificate Issue: hostname mismatch", issue.Issue(Vulnerabilities.TLS_CERT_HOSTNAME_MISMATCH, url, {"cert": cert}), ) if cert["issues"] & (1 << 4) != 0: reporter.display( "\tCertificate Issue: revoked", issue.Issue(Vulnerabilities.TLS_CERT_REVOKED, url, {"cert": cert}), ) if cert["issues"] & (1 << 5) != 0: reporter.display( "\tCertificate Issue: bad common name", issue.Issue(Vulnerabilities.TLS_CERT_BAD_COMMON_NAME, url, {"cert": cert}), ) if cert["issues"] & (1 << 6) != 0: reporter.display( "\tCertificate Issue: self-signed", issue.Issue(Vulnerabilities.TLS_CERT_SELF_SIGNED, url, {"cert": cert}), ) if cert["issues"] & (1 << 7) != 0: reporter.display( "\tCertificate Issue: blacklisted", issue.Issue(Vulnerabilities.TLS_CERT_BLACKLISTED, url, {"cert": cert}), ) if cert["issues"] & (1 << 8) != 0: reporter.display( "\tCertificate Issue: insecure signature", issue.Issue(Vulnerabilities.TLS_CERT_INSECURE_SIGNATURE, url, {"cert": cert}), ) if cert["issues"] & (1 << 9) != 0: reporter.display( "\tCertificate Issue: insecure key", issue.Issue(Vulnerabilities.TLS_CERT_INSECURE_KEY, url, {"cert": cert}), ) output.empty() output.norm(f'\tSubject: {cert["subject"]}') output.norm(f'\tCommon Names: {" ".join(cert["commonNames"])}') output.norm("\tAlternative names:") for name in cert["altNames"]: output.norm(f"\t\t{name}") output.norm(f'\tNot Before: {x509_cert.not_valid_before.isoformat(" ")}') output.norm(f'\tNot After: {x509_cert.not_valid_after.isoformat(" ")}') if cert["keyAlg"] == "EC": output.norm( f'\tKey: {cert["keyAlg"]} {cert["keySize"]} (RSA equivalent: {cert["keyStrength"]})' ) else: if cert["keySize"] < 2048: output.vuln(f'\tKey: {cert["keyAlg"]} {cert["keySize"]}') else: output.norm(f'\tKey: {cert["keyAlg"]} {cert["keySize"]}') # TODO: Public Key Hash serial = format(x509_cert.serial_number, "02x") output.norm(f"\tSerial: {serial}") output.norm(f'\tIssuer: {cert["issuerSubject"]}') if "validationType" in cert: if cert["validationType"] == "E": output.norm("\tExtended Validation: Yes") elif cert["validationType"] == "D": output.norm("\tExtended Validation: No (Domain Control)") else: output.norm( f'\tExtended Validation: No ({cert["validationType"]})') if cert["sct"]: # check the first bit, SCT in cert if ep["details"]["hasSct"] & 1 != 0: output.norm("\tCertificate Transparency: SCT in certificate") # check second bit, SCT in stapled OSCP response if ep["details"]["hasSct"] & (1 << 1) != 0: output.norm( "\tCertificate Transparency: SCT in the stapled OCSP response") # check third bit, SCT in the TLS extension if ep["details"]["hasSct"] & (1 << 2) != 0: output.norm( "\tCertificate Transparency: SCT in the TLS extension (ServerHello)" ) else: output.norm("\tCertificate Transparency: No") output.norm(f'\tOCSP Must Staple: {cert["mustStaple"]}') if cert["revocationInfo"] & 1 != 0: output.norm("\tRevocation information: CRL information available") if cert["revocationInfo"] & (1 << 1) != 0: output.norm("\tRevocation information: OCSP information available") if cert["revocationStatus"] == 0: output.norm("\tRevocation Status: not checked") elif cert["revocationStatus"] == 1: output.vuln('\tRevocation Status: certificate revoked"') elif cert["revocationStatus"] == 2: output.norm("\tRevocation Status: certificate not revoked") elif cert["revocationStatus"] == 3: output.warn("\tRevocation Status: revocation check error") elif cert["revocationStatus"] == 4: output.warn("\tRevocation Status: no revocation information") elif cert["revocationStatus"] == 5: output.error("\tRevocation Status: SSL Labs internal error") else: output.error( f'\tRevocation Status: Unknown response ({cert["revocationStatus"]})' ) if "crlRevocationStatus" in cert: if cert["crlRevocationStatus"] == 0: output.norm("\tCRL Revocation Status: not checked") elif cert["crlRevocationStatus"] == 1: output.vuln('\tCRL Revocation Status: certificate revoked"') elif cert["crlRevocationStatus"] == 2: output.norm("\tCRL Revocation Status: certificate not revoked") elif cert["crlRevocationStatus"] == 3: output.warn("\tCRL Revocation Status: revocation check error") elif cert["crlRevocationStatus"] == 4: output.warn("\tCRL Revocation Status: no revocation information") elif cert["crlRevocationStatus"] == 5: output.error("\tCRL Revocation Status: SSL Labs internal error") else: output.error( f'\tCRL Revocation Status: Unknown response ({cert["crlRevocationStatus"]})' ) else: output.norm("\tCRL Revocation Status: Not Provided") if "ocspRevocationStatus" in cert: if cert["ocspRevocationStatus"] == 0: output.norm("\tOCSP Revocation Status: not checked") elif cert["ocspRevocationStatus"] == 1: output.vuln('\tOCSP Revocation Status: certificate revoked"') elif cert["ocspRevocationStatus"] == 2: output.norm("\tOCSP Revocation Status: certificate not revoked") elif cert["ocspRevocationStatus"] == 3: output.warn("\tOCSP Revocation Status: revocation check error") elif cert["ocspRevocationStatus"] == 4: output.warn("\tOCSP Revocation Status: no revocation information") elif cert["ocspRevocationStatus"] == 5: output.error("\tOCSP Revocation Status: SSL Labs internal error") else: output.error( f'\tOCSP Revocation Status: Unknown response ({cert["ocspRevocationStatus"]})' ) else: output.norm("\tOCSP Revocation Status: Not Provided") output.empty() exts = cert_info.format_extensions(x509_cert) for ext in exts: output.norm(f"\tExtensions: {ext}") output.empty() scts = cert_info.get_scts(x509_cert) for sct in scts: output.norm( f'\tSCT: {cert_info.get_ct_log_name(sct[1])} - {sct[2].isoformat(" ")}' ) output.empty() cert_hash = bytes.hex(x509_cert.fingerprint(hashes.SHA1())) output.norm(f"\tFingerprint: {cert_hash}") output.norm(f"\t\thttps://censys.io/certificates?q={cert_hash}") output.norm(f"\t\thttps://crt.sh/?q={cert_hash}") output.empty() output.norm("\tCertificate Chains:") for chain in ep["details"]["certChains"]: path_count = 0 # build list of trust paths trust_paths = {} for path in chain["trustPaths"]: trusts = None # in practice, it seems there is only only per path, but just in case for trust in path["trust"]: if trust["isTrusted"]: trust_line = f'{trust["rootStore"]} (trusted)' else: trust_line = f'{trust["rootStore"]} ({trust["trustErrorMessage"]})' if trusts is None: trusts = trust_line else: trusts += f" {trust_line}" if trust_paths.get(tuple(path["certIds"])) is not None: trust_paths[tuple(path["certIds"])] += f" {trusts}" else: trust_paths[tuple(path["certIds"])] = trusts # process each of the trust paths for key in trust_paths.keys(): path_count += 1 output.norm(f"\t Path {path_count}:") output.norm(f"\t Root Stores: {trust_paths[key]}") if chain["issues"] & (1 << 1) != 0: output.warn("\tCertificate Chain Issue: incomplete chain") if chain["issues"] & (1 << 2) != 0: output.warn( "\tCertificate Chain Issue: chain contains unrelated/duplicate certificates" ) if chain["issues"] & (1 << 3) != 0: output.warn("\tCertificate Chain Issue: incorrect order") if chain["issues"] & (1 << 4) != 0: output.warn("\tCertificate Chain Issue: contains anchor") if cert["issues"] & (1 << 5) != 0: output.warn("\tCertificate Chain Issue: untrusted") for path_cert in key: for c in body["certs"]: if c["id"] == path_cert: output.norm(f'\t\t{c["subject"]}') output.norm( f'\t\t Signature: {c["sigAlg"]} Key: {c["keyAlg"]}-{c["keySize"]}' ) if cert_info.check_symantec_root(c["sha256Hash"]): reporter.display( "\t\t Untrusted Symantec Root", issue.Issue( Vulnerabilities.TLS_SYMANTEC_ROOT, url, {"fingerprint": c["sha1Hash"]}, ), ) output.norm(f'\t\t https://crt.sh/?q={c["sha1Hash"]}') if c["sha256Hash"] not in chain["certIds"]: output.norm("\t\t (provided by server)") output.empty()
def scan(args: Namespace, url: str, domain: str): reporter.register_data("url", url) reporter.register_data("domain", domain) output.empty() output.norm("HEAD:") head = network.http_head(url) raw = network.http_build_raw_response(head) for line in raw.splitlines(): output.norm(f"\t{line}") output.empty() res = http_basic.get_header_issues(head, raw, url) if len(res) > 0: output.norm("Header Issues:") reporter.display_results(res, "\t") output.empty() res = http_basic.get_cookie_issues(head, raw, url) if len(res) > 0: output.norm("Cookie Issues:") reporter.display_results(res, "\t") output.empty() # check for WAF signatures res = waf.get_waf(head.headers, raw, url) if len(res) > 0: output.norm("WAF Detection:") reporter.display_results(res, "\t") output.empty() output.norm("Performing vulnerability scan (this will take a while)...") links: List[str] = [] with Spinner(): try: links, res = spider.spider(url) except Exception as error: output.debug_exception() output.error(f"Error running scan: {str(error)}") output.norm(f"Identified {len(links) + 1} pages.") output.empty() if len(res) > 0: output.norm("Issues Detected:") reporter.display_results(res, "\t") output.empty() # get files, and add those to the link list links += _file_search(args, url, links) res = apache_httpd.check_all(url) if len(res) > 0: reporter.display_results(res, "\t") res = apache_tomcat.check_all(url, links) if len(res) > 0: reporter.display_results(res, "\t") res = nginx.check_all(url) if len(res) > 0: reporter.display_results(res, "\t") res = iis.check_all(url) if len(res) > 0: reporter.display_results(res, "\t") res = http_basic.check_propfind(url) if len(res) > 0: reporter.display_results(res, "\t") res = http_basic.check_trace(url) if len(res) > 0: reporter.display_results(res, "\t") res = http_basic.check_options(url) if len(res) > 0: reporter.display_results(res, "\t") wp_path, res = wordpress.identify(url) if len(res) > 0: reporter.display_results(res, "\t") if wp_path is not None: res = wordpress.check_json_user_enum(wp_path) if len(res) > 0: reporter.display_results(res, "\t")
def scan(args: Namespace, url: str, domain: str): output.norm( f"Beginning SSL scan using sslyze {__version__} (this could take a minute or two)" ) output.empty() ips = basic.get_ips(domain) for ip in ips: try: conn_tester = server_connectivity_tester.ServerConnectivityTester( hostname=domain, port=utils.get_port(url), ip_address=ip) output.norm(f"IP: {conn_tester.ip_address}:{conn_tester.port}") server_info = conn_tester.perform() scanner = synchronous_scanner.SynchronousScanner() cinfo = scanner.run_scan_command( server_info, certificate_info_plugin.CertificateInfoScanCommand()) cinfo = typing.cast( certificate_info_plugin.CertificateInfoScanResult, cinfo) # print info on the server cert _get_leaf_cert_info(cinfo.verified_certificate_chain[0]) # get all but the first element _get_cert_chain(cinfo.verified_certificate_chain[1:], url) # list the root stores this is trusted by trust = "" for t in _get_trusted_root_stores(cinfo): trust += f"{t} (trusted) " output.norm(f"\tRoot Stores: {trust}") output.empty() # get info for the various versions of SSL/TLS output.norm("\tCipher Suite Support:") sslv2 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Sslv20ScanCommand()) sslv2 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, sslv2) _get_suite_info("SSLv2", sslv2, url) sslv3 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Sslv30ScanCommand()) sslv3 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, sslv3) _get_suite_info("SSLv3", sslv3, url) tls10 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Tlsv10ScanCommand()) tls10 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, tls10) _get_suite_info("TLSv1.0", tls10, url) tls11 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Tlsv11ScanCommand()) tls11 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, tls11) _get_suite_info("TLSv1.1", tls11, url) tls12 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Tlsv12ScanCommand()) tls12 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, tls12) _get_suite_info("TLSv1.2", tls12, url) tls13 = scanner.run_scan_command( server_info, openssl_cipher_suites_plugin.Tlsv13ScanCommand()) tls13 = typing.cast( openssl_cipher_suites_plugin.CipherSuiteScanResult, tls13) _get_suite_info("TLSv1.3", tls13, url) output.empty() # check compression compression = scanner.run_scan_command( server_info, compression_plugin.CompressionScanCommand()) compression = typing.cast(compression_plugin.CompressionScanResult, compression) if compression.compression_name is not None: reporter.display( f"\tCompression: {compression.compression_name}", issue.Issue(Vulnerabilities.TLS_COMPRESSION_ENABLED, url, {}), ) else: output.norm("\tCompression: None") # check TLS_FALLBACK_SCSV fallback = scanner.run_scan_command( server_info, fallback_scsv_plugin.FallbackScsvScanCommand()) fallback = typing.cast(fallback_scsv_plugin.FallbackScsvScanResult, fallback) if fallback.supports_fallback_scsv: output.norm("\tDowngrade Prevention: Yes") else: reporter.display( "\tDowngrade Prevention: No", issue.Issue(Vulnerabilities.TLS_FALLBACK_SCSV_MISSING, url, {}), ) # check Heartbleed heartbleed = scanner.run_scan_command( server_info, heartbleed_plugin.HeartbleedScanCommand()) heartbleed = typing.cast(heartbleed_plugin.HeartbleedScanResult, heartbleed) if heartbleed.is_vulnerable_to_heartbleed: reporter.display( "\tHeartbleed: Vulnerable", issue.Issue(Vulnerabilities.TLS_HEARTBLEED, url, {}), ) else: output.norm("\tHeartbleed: No") # check OpenSSL CCS injection vulnerability (CVE-2014-0224) openssl_ccs = scanner.run_scan_command( server_info, openssl_ccs_injection_plugin.OpenSslCcsInjectionScanCommand(), ) openssl_ccs = typing.cast( openssl_ccs_injection_plugin.OpenSslCcsInjectionScanResult, openssl_ccs) if openssl_ccs.is_vulnerable_to_ccs_injection: reporter.display( "\tOpenSSL CCS (CVE-2014-0224): Vulnerable", issue.Issue(Vulnerabilities.TLS_OPENSSL_CVE_2014_0224, url, {}), ) else: output.norm("\tOpenSSL CCS (CVE-2014-0224): No") # check SessionRenegotiation sr = scanner.run_scan_command( server_info, session_renegotiation_plugin.SessionRenegotiationScanCommand(), ) sr = typing.cast( session_renegotiation_plugin.SessionRenegotiationScanResult, sr) if sr.accepts_client_renegotiation: output.norm( "\tSecure Renegotiation: client-initiated renegotiation supported" ) if sr.supports_secure_renegotiation: output.norm( "\tSecure Renegotiation: secure renegotiation supported") # check SessionResumption resump = scanner.run_scan_command( server_info, session_resumption_plugin.SessionResumptionSupportScanCommand( ), ) resump = typing.cast( session_resumption_plugin.SessionResumptionSupportScanResult, resump) output.norm( f"\tSession Resumption Tickets Supported: {resump.is_ticket_resumption_supported}" ) output.norm( f"\tSession Resumption: {resump.successful_resumptions_nb} of " f"{resump.attempted_resumptions_nb} successful") # check ROBOT robot = scanner.run_scan_command(server_info, robot_plugin.RobotScanCommand()) robot = typing.cast(robot_plugin.RobotScanResult, robot) if (robot.robot_result_enum == robot_plugin.RobotScanResultEnum.VULNERABLE_WEAK_ORACLE): reporter.display( "\tROBOT: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_ROBOT_ORACLE_WEAK, url, {}), ) elif (robot.robot_result_enum == robot_plugin.RobotScanResultEnum.VULNERABLE_STRONG_ORACLE): reporter.display( "\tROBOT: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_ROBOT_ORACLE_STRONG, url, {}), ) elif (robot.robot_result_enum == robot_plugin.RobotScanResultEnum. UNKNOWN_INCONSISTENT_RESULTS): output.error("\tROBOT: Test Failed (Inconsistent Results)") else: output.norm("\tROBOT: No") # check TLS 1.3 Early Data ed = scanner.run_scan_command( server_info, early_data_plugin.EarlyDataScanCommand()) ed = typing.cast(early_data_plugin.EarlyDataScanResult, ed) if ed.is_early_data_supported: output.info("\tTLS 1.3 0-RTT Support: Yes") else: output.norm("\tTLS 1.3 0-RTT Support: No") if cinfo.ocsp_response_status is not None: output.norm("\tOCSP Stapling: Yes") else: reporter.display( "\tOCSP Stapling: No", issue.Issue(Vulnerabilities.TLS_OCSP_STAPLE_MISSING, url, {}), ) output.empty() except server_connectivity_tester.ServerConnectivityError as error: output.debug_exception() if checkers.is_ipv6(ip): output.error( "\tError connecting to IPv6 IP. Please ensure that your system is configured properly." ) output.error(f"\tConnection failed ({str(error)})") output.empty()
def scan(session: Session): reporter.register_data("url", session.url) reporter.register_data("domain", session.domain) output.empty() output.norm("HEAD:") head = network.http_head(session.url) raw = network.http_build_raw_response(head) for line in raw.splitlines(): output.norm(f"\t{line}") output.empty() res = http_basic.get_header_issues(head, raw, session.url) if res: output.norm("Header Issues:") reporter.display_results(res, "\t") output.empty() res = http_basic.get_cookie_issues(head, session.url) if res: output.norm("Cookie Issues:") reporter.display_results(res, "\t") output.empty() # check for WAF signatures res = waf.get_waf(head.headers, raw, session.url) if res: output.norm("WAF Detection:") reporter.display_results(res, "\t") output.empty() output.norm("Performing vulnerability scan (this will take a while)...") links: List[str] = [] with Spinner(): try: links, res = spider.spider(session.url) except Exception as error: output.debug_exception() output.error(f"Error running scan: {str(error)}") output.norm(f"Identified {len(links) + 1} pages.") output.empty() if res: output.norm("Issues Detected:") reporter.display_results(res, "\t") output.empty() # get files, and add those to the link list links += _file_search(session, links) if ( session.args.pass_reset_page is not None and len(session.args.pass_reset_page) > 0 ): _check_password_reset(session) with Spinner(): res = http_basic.check_local_ip_disclosure(session) if res: reporter.display_results(res, "\t") with Spinner(): res = apache_httpd.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = apache_tomcat.check_all(session.url, links) if res: reporter.display_results(res, "\t") with Spinner(): res = nginx.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = iis.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_propfind(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_trace(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_options(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = php.find_phpinfo(links) if res: reporter.display_results(res, "\t") with Spinner(): res, jira_path = jira.check_for_jira(session) if res: reporter.display_results(res, "\t") if jira_path is not None: with Spinner(): res = jira.check_jira_user_registration(jira_path) if res: reporter.display_results(res, "\t") with Spinner(): wp_path, res = wordpress.identify(session.url) if res: reporter.display_results(res, "\t") if wp_path is not None: with Spinner(): res = wordpress.check_json_user_enum(wp_path) res += wordpress.check_path_disclosure(wp_path) if res: reporter.display_results(res, "\t")
def scan(session: Session): reporter.register_data("url", session.url) reporter.register_data("domain", session.domain) # check to see if this is an IP, if so, bail out if utils.is_ip(session.domain): return output.empty() output.norm("DNS Information:") # get the root domain, by looking up via the PSL psl = PublicSuffixList() root_domain = psl.privatesuffix(session.domain) reporter.register_data("root_domain", root_domain) # IP Addresses for the domain we are scanning ips = basic.get_ips(session.domain) reporter.register_data("ip", ips) for ip in ips: output.norm("\t%s (%s)" % (ip, basic.get_host(str(ip)))) addr = ipaddress.ip_address(str(ip)) if not addr.is_private: ni = network_info.network_info(str(ip)) output.norm("\t\t%s" % ni) if addr.version == 4: output.norm("\t\thttps://www.shodan.io/host/%s" % ip) output.norm("\t\thttps://censys.io/ipv4/%s" % ip) else: output.norm("\t\thttps://www.shodan.io/host/%s" % str(ip).lower()) output.empty() # TXT records for the domain we are scanning try: txt = basic.get_text(session.domain) reporter.register_data("dns_txt", {session.domain: txt}) for rec in txt: output.norm("\tTXT: %s" % rec) except Exception as err: output.error(f"Error getting TXT records: {str(err)}") # TXT records for the root domain try: if root_domain != session.domain: txt = basic.get_text(root_domain) reporter.register_data("dns_txt", {root_domain: txt}) for rec in txt: output.norm("\tTXT (%s): %s" % (root_domain, rec)) except Exception as err: output.error(f"Error getting TXT (root) records: {str(err)}") output.empty() # MX records for the domain we are scanning try: mx = basic.get_mx(session.domain) reporter.register_data("dns_mx", {session.domain: mx}) for rec in mx: server_ip, ni = _get_ip_info(rec[0]) info = "%s (%s) - %s (%s)" % (rec[0], rec[1], server_ip, ni) output.norm("\tMX: %s" % info) except Exception as err: output.error(f"Error getting MX records: {str(err)}") try: # MX records for the root domain if root_domain != session.domain: mx = basic.get_mx(root_domain) reporter.register_data("dns_mx", {root_domain: mx}) for rec in mx: server_ip, ni = _get_ip_info(rec[0]) info = "%s (%s) - %s (%s)" % (rec[0], rec[1], server_ip, ni) output.norm("\tMX (%s): %s" % (root_domain, info)) except Exception as err: output.error(f"Error getting MX (root) records: {str(err)}") output.empty() # NS records for the root domain try: ns = basic.get_ns(root_domain) reporter.register_data("dns_ns", {root_domain: ns}) for rec in ns: server_ip, ni = _get_ip_info(rec) info = "%s - %s (%s)" % (rec, server_ip, ni) output.norm("\tNS: %s" % info) except Exception as err: output.error(f"Error getting NS records: {str(err)}") output.empty() if session.args.srv: try: output.norm( "Searching for SRV records, this will take a minute...") output.empty() with Spinner(): srv_records = srv.find_srv_records(root_domain) reporter.register_data("dns_srv", srv_records) for rec in srv_records: server_ip, ni = _get_ip_info(rec[1]) info = "%s: %s:%s - %s (%s)" % (rec[0], rec[1], rec[2], server_ip, ni) output.norm("\tSRV: %s" % info) output.empty() except Exception as err: output.error(f"Error getting SRV records: {str(err)}") if session.args.subdomains: try: output.norm( "Searching for sub-domains, this will take a few minutes...") output.empty() with Spinner(): sds = subdomains.find_subdomains(root_domain) reporter.register_data("dns_subdomains", sds) for rec in sds: info = "" if rec[0] == "CNAME": server_ip, ni = _get_ip_info(rec[2]) info = "(CNAME) %s -> %s - %s (%s)" % ( rec[1], rec[2], server_ip, ni, ) elif rec[0] == "A": ni = network_info.network_info(rec[2]) info = "(A) %s: %s (%s)" % (rec[1], rec[2], ni) elif rec[0] == "AAAA": ni = network_info.network_info(rec[2]) info = "(AAAA) %s: %s (%s)" % (rec[1], rec[2], ni) output.norm("\tSubdomain: %s" % info) except Exception as err: output.error(f"Error getting subdomain records: {str(err)}") output.empty() try: caa_count = 0 carec = caa.get_caa(session.domain) reporter.register_data("dns_caa", carec) for rec in carec: curr = rec[0] if rec[1] == "CNAME": output.norm("\tCAA (%s): CNAME Found: -> %s" % (curr, rec[2])) elif rec[1] == "CAA": if len(rec[2]) > 0: for line in rec[2]: output.norm('\tCAA (%s): "%s"' % (curr, line)) caa_count += 1 else: output.norm("\tCAA (%s): No Records Found" % curr) # notify the user if there's an issue if caa_count == 0: reporter.display( "\tCAA: Domain does not have protection from CAA", issue.Issue(Vulnerabilities.DNS_CAA_MISSING, session.url, {"caa_records": carec}), ) except Exception as err: output.error(f"Error getting CAA records: {str(err)}") output.empty() try: dk = dnssec.get_dnskey(session.domain) reporter.register_data("dns_dnskey", dk) if len(dk) > 0: for rec in dk: output.norm( "\tDNSKEY: Algorithm: '%s' - Flags: '%s' - Key Length: %s" % (rec[2], rec[0], len(rec[3]) * 8)) else: reporter.display( "\tDNSKEY: Domain does not use DNSSEC", issue.Issue(Vulnerabilities.DNS_DNSSEC_NOT_ENABLED, session.url, {}), ) except Exception as err: output.error(f"Error getting DNSKEY records: {str(err)}") output.empty()
def scan(session: Session): reporter.register_data("url", session.url) reporter.register_data("domain", session.domain) output.empty() output.norm("HEAD:") head = network.http_head(session.url) raw = network.http_build_raw_response(head) for line in raw.splitlines(): output.norm(f"\t{line}") output.empty() res = http_basic.get_header_issues(head, raw, session.url) if res: output.norm("Header Issues:") reporter.display_results(res, "\t") output.empty() res = http_basic.get_cookie_issues(head, session.url) if res: output.norm("Cookie Issues:") reporter.display_results(res, "\t") output.empty() # check for WAF signatures res = waf.get_waf(head.headers, raw, session.url) if res: output.norm("WAF Detection:") reporter.display_results(res, "\t") output.empty() # check the HSTS preload status results = http_basic.check_hsts_preload(session.url) if len(results) > 0: reporter.register_data("hsts_preload_status", results) output.norm("HSTS Preload Status:") for result in results: chrome = result["chrome"] is not None firefox = result["firefox"] is not None tor = result["tor"] is not None output.norm( f"\t({result['domain']}) Chrome: {chrome}\tFirefox: {firefox}\t\tTor: {tor}" ) output.empty() methods, res = http_basic.check_http_methods(session.url) if len(methods) == 0: output.norm("Server responds to invalid HTTP methods - check skipped.") else: reporter.register_data({"http_methods_supported": methods}) output.norm("Supported HTTP methods:") for method in methods: output.norm(f"\t{method}") output.empty() if res: reporter.display_results(res, "\t") output.empty() output.norm("Performing vulnerability scan (this will take a while)...") links: List[str] = [] with Spinner(): try: links, res = spider.spider(session.url) except Exception as error: output.debug_exception() output.error(f"Error running scan: {str(error)}") output.norm(f"Identified {len(links) + 1} pages.") output.empty() if res: output.norm("Issues Detected:") reporter.display_results(res, "\t") output.empty() # get files, and add those to the link list links += _file_search(session, links) if ( session.args.pass_reset_page is not None and len(session.args.pass_reset_page) > 0 ): _check_password_reset(session) with Spinner(): res = http_basic.check_local_ip_disclosure(session) if res: reporter.display_results(res, "\t") with Spinner(): res = apache_httpd.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = apache_tomcat.check_all(session.url, links) if res: reporter.display_results(res, "\t") with Spinner(): res = nginx.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = iis.check_all(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_propfind(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_trace(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = http_basic.check_options(session.url) if res: reporter.display_results(res, "\t") with Spinner(): res = php.find_phpinfo(links) if res: reporter.display_results(res, "\t") if session.args.php_page is not None and len(session.args.php_page) > 0: with Spinner(): res = php.check_cve_2019_11043(session, links) if res: reporter.display_results(res, "\t") with Spinner(): res, jira_path = jira.check_for_jira(session) if res: reporter.display_results(res, "\t") if jira_path is not None: with Spinner(): res = jira.check_jira_user_registration(jira_path) if res: reporter.display_results(res, "\t") with Spinner(): wp_path, res = wordpress.identify(session.url) if res: reporter.display_results(res, "\t") if wp_path is not None: with Spinner(): res = wordpress.check_json_user_enum(wp_path) res += wordpress.check_path_disclosure(wp_path) if res: reporter.display_results(res, "\t")
def scan(session: Session): ips = basic.get_ips(session.domain) for ip in ips: conn = None count = 0 try: count = 0 conn_tester = server_connectivity_tester.ServerConnectivityTester( hostname=session.domain, port=utils.get_port(session.url), ip_address=ip) output.norm( f"TLS Session Request Limit: Checking number of requests accepted using 3DES suites " f"(IP: {conn_tester.ip_address}:{conn_tester.port})") server_info = conn_tester.perform() conn = ssl_connection_configurator.SslConnectionConfigurator.get_connection( ssl_version=OpenSslVersionEnum.SSLV23, server_info=server_info, should_use_legacy_openssl=True, openssl_cipher_string="3DES", should_ignore_client_auth=True, ssl_verify_locations=None, ) conn.connect() req = ("HEAD / HTTP/1.1\r\n" "Host: {host}\r\n" "User-Agent: {user_agent}\r\n" "Accept: */*\r\n" "Connection: keep-alive\r\n\r\n".format( host=session.domain, user_agent=network.YAWAST_UA)) ossl_name = conn.ssl_client.get_current_cipher_name() name = OPENSSL_TO_RFC_NAMES_MAPPING[OpenSslVersionEnum.TLSV1].get( ossl_name, ossl_name) print(" ", end="", flush=True) print(f"(using {name})", end="", flush=True) for i in range(0, 10000): conn.ssl_client.write(req) http_response_parser.HttpResponseParser.parse_from_ssl_connection( conn.ssl_client) count += 1 if i % 20: print(".", end="", flush=True) output.empty() reporter.display( f"\tTLS Session Request Limit: Connection not terminated after {count} requests; " f"possibly vulnerable to SWEET32", issue.Issue(Vulnerabilities.TLS_SWEET32, session.url, {}), ) except ssl_connection.SslHandshakeRejected as error: output.debug_exception() output.empty() output.norm(f"\tServer rejected our connection ({str(error)})") output.empty() except IOError as error: output.debug_exception() output.empty() if count > 0: output.norm( f"\tTLS Session Request Limit: Connection terminated after {count} requests ({str(error)})" ) else: output.norm( "\tTLS Session Request Limit: Server does not support 3DES cipher suites" ) output.empty() except server_connectivity_tester.ServerConnectivityError as error: output.debug_exception() output.empty() if checkers.is_ipv6(ip): output.error( "\tError connecting to IPv6 IP. Please ensure that your system is configured properly." ) output.error(f"\tConnection failed ({str(error)})") output.empty() finally: if conn is not None: conn.close() output.empty()
def _get_vulnerability_info(ep, url): output.norm("\tProtocol & Vulnerability Information:") if "sniRequired" in ep["details"]: if ep["details"]["sniRequired"]: output.info("\t\tSNI Required: Yes") else: output.norm("\t\tSNI Required: No") if "drownVulnerable" in ep["details"]: if ep["details"]["drownVulnerable"]: output.vuln("\t\tDROWN: Vulnerable") servers = "" for dh in ep["details"]["drownHosts"]: servers += f'({dh["ip"]}:{dh["port"]} - {dh["status"]}) ' output.norm(f'\t\t\t{dh["ip"]}:{dh["port"]} - {dh["status"]}') output.norm( f'\t\t\t https://test.drownattack.com/?site={dh["ip"]}') reporter.register( issue.Issue( Vulnerabilities.TLS_DROWN, url, { "servers": servers, "ip": ep["ipAddress"] }, )) else: output.norm("\t\tDROWN: No") else: output.error("\t\tDROWN: Information Not Received") if "zeroRTTEnabled" in ep["details"]: if ep["details"]["zeroRTTEnabled"] == -2: output.error("\t\tTLS 1.3 0-RTT Support: Test Failed") elif ep["details"]["zeroRTTEnabled"] == -1: output.norm("\t\tTLS 1.3 0-RTT Support: Test Not Performed") elif ep["details"]["zeroRTTEnabled"] == 0: output.norm("\t\tTLS 1.3 0-RTT Support: No") elif ep["details"]["zeroRTTEnabled"] == 1: reporter.display( "\t\tTLS 1.3 0-RTT Support: Yes", issue.Issue( Vulnerabilities.TLS_VERSION_1_3_EARLY_DATA_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tTLS 1.3 0-RTT Support: Unknown ({ep["details"]["zeroRTTEnabled"]})' ) else: output.error("\t\tTLS 1.3 0-RTT Support: Information Not Received") if "renegSupport" in ep["details"]: if ep["details"]["renegSupport"] & 1 != 0: reporter.display( "\t\tSecure Renegotiation: insecure client-initiated renegotiation supported", issue.Issue(Vulnerabilities.TLS_INSECURE_RENEG, url, {"ip": ep["ipAddress"]}), ) if ep["details"]["renegSupport"] & (1 << 1) != 0: output.norm( "\t\tSecure Renegotiation: secure renegotiation supported") if ep["details"]["renegSupport"] & (1 << 2) != 0: output.norm( "\t\tSecure Renegotiation: secure client-initiated renegotiation supported" ) if ep["details"]["renegSupport"] & (1 << 3) != 0: output.norm( '\t\tSecure Renegotiation: server requires secure renegotiation support"' ) else: output.error("\t\tSecure Renegotiation: Information Not Received") if "poodle" in ep["details"]: if ep["details"]["poodle"]: reporter.display( "\t\tPOODLE (SSL): Vulnerable", issue.Issue(Vulnerabilities.TLS_LEGACY_SSL_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tPOODLE (SSL): No") else: output.error("\t\tPOODLE (SSL): Information Not Received") if "zombiePoodle" in ep["details"]: if ep["details"]["zombiePoodle"] == -1: output.error("\t\tZombie POODLE: Test Failed") elif ep["details"]["zombiePoodle"] == 0: output.error("\t\tZombie POODLE: Test Failed (Unknown)") elif ep["details"]["zombiePoodle"] == 1: output.norm("\t\tZombie POODLE: No") elif ep["details"]["zombiePoodle"] == 2: reporter.display( "\t\tZombie POODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_ZOMBIE_POODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["zombiePoodle"] == 3: output.vuln("\t\tZombie POODLE: Vulnerable - Exploitable") reporter.display( "\t\tZombie POODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_ZOMBIE_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tZombie POODLE: Unknown Response ({ep["details"]["zombiePoodle"]})' ) else: output.error("\t\tZombie POODLE: Information Not Received") if "goldenDoodle" in ep["details"]: if ep["details"]["goldenDoodle"] == -1: output.error("\t\tGOLDENDOODLE: Test Failed") elif ep["details"]["goldenDoodle"] == 0: output.error("\t\tGOLDENDOODLE: Test Failed (Unknown)") elif ep["details"]["goldenDoodle"] == 1: output.norm("\t\tGOLDENDOODLE: No") elif ep["details"]["goldenDoodle"] == 4: reporter.display( "\t\tGOLDENDOODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_GOLDENDOODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["goldenDoodle"] == 5: reporter.display( "\t\tGOLDENDOODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_GOLDENDOODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f't\tGOLDENDOODLE: Unknown Response ({ep["details"]["goldenDoodle"]})' ) else: output.error("\t\tGOLDENDOODLE: Information Not Received") if "zeroLengthPaddingOracle" in ep["details"]: if ep["details"]["zeroLengthPaddingOracle"] == -1: output.error( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Test Failed" ) elif ep["details"]["zeroLengthPaddingOracle"] == 0: output.error( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Test Failed (Unknown)" ) elif ep["details"]["zeroLengthPaddingOracle"] == 1: output.norm( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): No") elif ep["details"]["zeroLengthPaddingOracle"] == 6: reporter.display( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Vulnerable - Not Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2019_1559_NE, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["zeroLengthPaddingOracle"] == 7: reporter.display( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Vulnerable - Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2019_1559, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f"\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Unknown Response" f' ({ep["details"]["zeroLengthPaddingOracle"]})') else: output.error( "OpenSSL 0-Length Padding Oracle (CVE-2019-1559): Information Not Received" ) if "sleepingPoodle" in ep["details"]: if ep["details"]["sleepingPoodle"] == -1: output.error("\t\tSleeping POODLE: Test Failed") elif ep["details"]["sleepingPoodle"] == 0: output.error("\t\tSleeping POODLE: Test Failed (Unknown)") elif ep["details"]["sleepingPoodle"] == 1: output.norm("\t\tSleeping POODLE: No") elif ep["details"]["sleepingPoodle"] == 10: reporter.display( "\t\tSleeping POODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_SLEEPING_POODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["sleepingPoodle"] == 11: output.vuln("\t\tSleeping POODLE: Vulnerable - Exploitable") reporter.display( "\t\tSleeping POODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_SLEEPING_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tSleeping POODLE: Unknown Response ({ep["details"]["sleepingPoodle"]})' ) else: output.error("\t\tSleeping POODLE: Information Not Received") if "poodleTls" in ep["details"]: if ep["details"]["poodleTls"] == -3: output.info("\t\tPOODLE (TLS): Inconclusive (Timeout)") elif ep["details"]["poodleTls"] == -2: output.info("\t\tPOODLE (TLS): TLS Not Supported") elif ep["details"]["poodleTls"] == -1: output.error("\t\tPOODLE (TLS): Test Failed") elif ep["details"]["poodleTls"] == 0: output.error("\t\tPOODLE (TLS): Test Failed (Unknown)") elif ep["details"]["poodleTls"] == 1: output.norm("\t\tPOODLE (TLS): No") elif ep["details"]["poodleTls"] == 2: reporter.display( "\t\tPOODLE (TLS): Vulnerable", issue.Issue(Vulnerabilities.TLS_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tPOODLE (TLS): Unknown Response ({ep["details"]["poodleTls"]})' ) else: output.error("\t\tPOODLE (TLS): Information Not Received") if "fallbackScsv" in ep["details"]: if ep["details"]["fallbackScsv"]: output.norm("\t\tDowngrade Prevention: Yes") else: reporter.display( "\t\tDowngrade Prevention: No", issue.Issue( Vulnerabilities.TLS_FALLBACK_SCSV_MISSING, url, {"ip": ep["ipAddress"]}, ), ) else: output.error("\t\tDowngrade Prevention: Information Not Received") if "compressionMethods" in ep["details"]: if ep["details"]["compressionMethods"] & 1 != 0: reporter.display( "\t\tCompression: DEFLATE", issue.Issue( Vulnerabilities.TLS_COMPRESSION_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.norm("\t\tCompression: No") else: output.error("\t\tCompression: Information Not Received") if "heartbeat" in ep["details"]: if ep["details"]["heartbeat"]: reporter.display( "\t\tHeartbeat: Enabled", issue.Issue(Vulnerabilities.TLS_HEARTBEAT_ENABLED, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tHeartbeat: Disabled") else: output.error("\t\tHeartbeat: Information Not Received") if "heartbleed" in ep["details"]: if ep["details"]["heartbleed"]: reporter.display( "\t\tHeartbleed: Vulnerable", issue.Issue(Vulnerabilities.TLS_HEARTBLEEDL, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tHeartbleed: No") else: output.error("\t\tHeartbleed: Information Not Received") if "ticketbleed" in ep["details"]: if ep["details"]["ticketbleed"] == -1: output.error("\t\tTicketbleed (CVE-2016-9244): Test Failed") elif ep["details"]["ticketbleed"] == 0: output.error( "\t\tTicketbleed (CVE-2016-9244): Test Failed (Unknown)") elif ep["details"]["ticketbleed"] == 1: output.norm("\t\tTicketbleed (CVE-2016-9244): No") elif ep["details"]["ticketbleed"] == 2: reporter.display( "\t\tTicketbleed (CVE-2016-9244): Vulnerable", issue.Issue(Vulnerabilities.TLS_TICKETBLEED, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tTicketbleed (CVE-2016-9244): Unknown Response ({ep["details"]["ticketbleed"]})' ) else: output.error( "\t\tTicketbleed (CVE-2016-9244): Information Not Received") if "openSslCcs" in ep["details"]: if ep["details"]["openSslCcs"] == -1: output.error("\t\tOpenSSL CCS (CVE-2014-0224): Test Failed") elif ep["details"]["openSslCcs"] == 0: output.error( "\t\tOpenSSL CCS (CVE-2014-0224): Test Failed (Unknown)") elif ep["details"]["openSslCcs"] == 1: output.norm("\t\tOpenSSL CCS (CVE-2014-0224): No") elif ep["details"]["openSslCcs"] == 2: reporter.display( "\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable - Not Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2014_0224_NE, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["openSslCcs"] == 3: output.vuln("\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable") reporter.display( "\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2014_0224, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tOpenSSL CCS (CVE-2014-0224): Unknown Response ({ep["details"]["openSslCcs"]})' ) else: output.error( "\t\tOpenSSL CCS (CVE-2014-0224): Information Not Received") if "openSSLLuckyMinus20" in ep["details"]: if ep["details"]["openSSLLuckyMinus20"] == -1: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Test Failed") elif ep["details"]["openSSLLuckyMinus20"] == 0: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Test Failed (Unknown)" ) elif ep["details"]["openSSLLuckyMinus20"] == 1: output.norm("\t\tOpenSSL Padding Oracle (CVE-2016-2107): No") elif ep["details"]["openSSLLuckyMinus20"] == 2: reporter.display( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Vulnerable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2016_2107, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f"\t\tOpenSSL Padding Oracle (CVE-2016-2107): Unknown Response " f'({ep["details"]["openSSLLuckyMinus20"]})') else: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Information Not Received" ) if "bleichenbacher" in ep["details"]: if ep["details"]["bleichenbacher"] == -1: output.error("\t\tROBOT: Test Failed") elif ep["details"]["bleichenbacher"] == 0: output.error("\t\tROBOT: Test Failed (Unknown)") elif ep["details"]["bleichenbacher"] == 1: output.norm("\t\tROBOT: No") elif ep["details"]["bleichenbacher"] == 2: reporter.display( "\t\tROBOT: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_ROBOT_ORACLE_WEAK, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["bleichenbacher"] == 3: reporter.display( "\t\tROBOT: Vulnerable - Exploitable", issue.Issue( Vulnerabilities.TLS_ROBOT_ORACLE_STRONG, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["bleichenbacher"] == 4: output.norm("\t\tROBOT: Unknown - Inconsistent Results") else: output.error( f'\t\tROBOT: Unknown Response ({ep["details"]["bleichenbacher"]})' ) else: output.error("\t\tROBOT: Information Not Received") if "forwardSecrecy" in ep["details"]: if ep["details"]["forwardSecrecy"] & (1 << 2) != 0: output.norm("\t\tForward Secrecy: Yes (all simulated clients)") elif ep["details"]["forwardSecrecy"] & (1 << 1) != 0: output.info("\t\tForward Secrecy: Yes (modern clients)") elif ep["details"]["forwardSecrecy"] & 1 != 0: reporter.display( "\t\tForward Secrecy: Yes (limited support)", issue.Issue( Vulnerabilities.TLS_LIMITED_FORWARD_SECRECY, url, {"ip": ep["ipAddress"]}, ), ) else: output.vuln("\t\tForward Secrecy: No") else: output.error("\t\tForward Secrecy: Information Not Received") if "supportsAead" in ep["details"]: if ep["details"]["supportsAead"]: output.norm("\t\tAEAD Cipher Suites Supported: Yes") else: reporter.display( "\t\tAEAD Cipher Suites Supported: No", issue.Issue(Vulnerabilities.TLS_NO_AEAD_SUPPORT, url, {"ip": ep["ipAddress"]}), ) else: output.error( "\t\tAEAD Cipher Suites Supported: Information Not Received") if "supportsCBC" in ep["details"]: if ep["details"]["supportsCBC"]: output.info("\t\tCBC Cipher Suites Supported: Yes") else: output.norm("\t\tCBC Cipher Suites Supported: No") else: output.error( "\t\tCBC Cipher Suites Supported: Information Not Received") if "alpnProtocols" in ep["details"]: output.norm(f'\t\tALPN: {ep["details"]["alpnProtocols"]}') if "npnProtocols" in ep["details"]: output.norm(f'\t\tNPN: {ep["details"]["npnProtocols"]}') if "sessionResumption" in ep["details"]: if ep["details"]["sessionResumption"] == 0: output.norm("\t\tSession Resumption: Not Enabled / Empty Tickets") elif ep["details"]["sessionResumption"] == 1: output.norm("\t\tSession Resumption: Enabled / No Resumption") elif ep["details"]["sessionResumption"] == 2: reporter.display( "\t\tSession Resumption: Enabled", issue.Issue( Vulnerabilities.TLS_SESSION_RESP_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tSession Resumption: Unknown Response ({ep["details"]["sessionResumption"]})' ) else: output.error("\t\tSession Resumption: Information Not Received") if "ocspStapling" in ep["details"]: if ep["details"]["ocspStapling"]: output.norm("\t\tOCSP Stapling: Yes") else: reporter.display( "\t\tOCSP Stapling: No", issue.Issue( Vulnerabilities.TLS_OCSP_STAPLE_MISSING, url, {"ip": ep["ipAddress"]}, ), ) else: output.error("\t\tOCSP Stapling: Information Not Received") if "miscIntolerance" in ep["details"]: if ep["details"]["miscIntolerance"] & 1 != 0: output.info("\t\tTLS Extension Intolerance: Yes") if ep["details"]["miscIntolerance"] & (1 << 1) != 0: output.warn("\t\tLong Handshake Intolerance: Yes") if ep["details"]["miscIntolerance"] & (1 << 2) != 0: output.warn("\t\tLong Handshake Intolerance: Workaround Success") if "protocolIntolerance" in ep["details"]: if ep["details"]["protocolIntolerance"] & 1 != 0: output.warn("\t\tProtocol Intolerance: TLS 1.0") if ep["details"]["protocolIntolerance"] & (1 << 1) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.1") if ep["details"]["protocolIntolerance"] & (1 << 2) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.2") if ep["details"]["protocolIntolerance"] & (1 << 3) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.3") if ep["details"]["protocolIntolerance"] & (1 << 4) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.152") if ep["details"]["protocolIntolerance"] & (1 << 5) != 0: output.warn("\t\tProtocol Intolerance: TLS 2.152") if "freak" in ep["details"]: if ep["details"]["freak"]: reporter.display( "\t\tFREAK: Vulnerable (512-bit key exchange supported)", issue.Issue(Vulnerabilities.TLS_FREAK, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tFREAK: No") else: output.error("\t\tFREAK: Information Not Received") if "logjam" in ep["details"]: if ep["details"]["logjam"]: reporter.display( "\t\tLogjam: Vulnerable", issue.Issue(Vulnerabilities.TLS_LOGJAM, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tLogjam: No") else: output.error("\t\tLogjam: Information Not Received") if "dhUsesKnownPrimes" in ep["details"]: if ep["details"]["dhUsesKnownPrimes"] == 0: output.norm("\t\tUses common DH primes: No") elif ep["details"]["dhUsesKnownPrimes"] == 1: output.warn("\t\tUses common DH primes: Yes (not weak)") reporter.display( "\t\tUses common DH primes: Yes (weak)", issue.Issue( Vulnerabilities.TLS_DH_KNOWN_PRIMES_STRONG, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["dhUsesKnownPrimes"] == 2: reporter.display( "\t\tUses common DH primes: Yes (weak)", issue.Issue( Vulnerabilities.TLS_DH_KNOWN_PRIMES_WEAK, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tUses common DH primes: Unknown Response ({ep["details"]["dhUsesKnownPrimes"]})' ) if "dhYsReuse" in ep["details"]: if ep["details"]["dhYsReuse"]: reporter.display( "\t\tDH public server param (Ys) reuse: Yes", issue.Issue(Vulnerabilities.TLS_DH_PARAM_REUSE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tDH public server param (Ys) reuse: No") if "ecdhParameterReuse" in ep["details"]: if ep["details"]["ecdhParameterReuse"]: reporter.display( "\t\tECDH Public Server Param Reuse: Yes", issue.Issue(Vulnerabilities.TLS_ECDH_PARAM_REUSE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tECDH Public Server Param Reuse: No") output.empty()
def _file_search(session: Session, orig_links: List[str]) -> List[str]: new_files: List[str] = [] file_good, file_res, path_good, path_res = network.check_404_response(session.url) # these are here for data typing results: Union[List[Result], None] links: Union[List[str], None] if not file_good: reporter.display( "Web server does not respond properly to file 404 errors.", Issue( Vulnerabilities.SERVER_INVALID_404_FILE, session.url, Evidence.from_response(file_res), ), ) if not path_good: reporter.display( "Web server does not respond properly to path 404 errors.", Issue( Vulnerabilities.SERVER_INVALID_404_PATH, session.url, Evidence.from_response(path_res), ), ) if not (file_good or path_good): output.norm( "Site does not respond properly to non-existent file/path requests; skipping some checks." ) if file_good: links, results = special_files.check_special_files(session.url) if results: reporter.display_results(results, "\t") new_files += links if session.args.files: output.empty() output.norm("Searching for common files (this will take a few minutes)...") with Spinner(): try: links, results = file_search.find_files(session.url) except Exception as error: output.debug_exception() output.error(f"Error running scan: {str(error)}") results = None links = None if results is not None and results: reporter.display_results(results, "\t") if links is not None and links: new_files += links for l in links: if l not in orig_links: output.norm(f"\tNew file found: {l}") output.empty() # check for common backup files all_links = orig_links + new_files with Spinner(): backups, res = file_search.find_backups(all_links) if res: reporter.display_results(res, "\t") if backups: new_files += backups if path_good: links, results = special_files.check_special_paths(session.url) if results: reporter.display_results(results, "\t") new_files += links if session.args.dir: output.empty() output.norm( "Searching for common directories (this will take a few minutes)..." ) with Spinner(): try: links, results = file_search.find_directories( session.url, session.args.dirlistredir, session.args.dirrecursive, ) except Exception as error: output.debug_exception() output.error(f"Error running scan: {str(error)}") results = None links = None if results is not None and results: reporter.display_results(results, "\t") if links is not None and links: new_files += links for l in links: if l not in orig_links: output.norm(f"\tNew directory found: {l}") output.empty() # check for .DS_Store files if file_good: res = file_search.find_ds_store(new_files) if res: reporter.display_results(res, "\t") return new_files