Example #1
0
def start(session: Session):
    print(f"Scanning: {session.url}")

    # make sure it resolves
    try:
        socket.gethostbyname(session.domain)
    except socket.gaierror as error:
        print(
            f"Fatal Error: Unable to resolve {session.domain} ({str(error)})")

        return

    try:
        cutils.check_redirect(session)
    except ValueError as error:
        print(f"Unable to continue: {str(error)}")

        return

    # check to see if we are looking at an HTTPS server
    if session.url_parsed.scheme == "https":
        if (session.args.internalssl or utils.is_ip(session.domain)
                or utils.get_port(session.url) != 443):
            # use internal scanner
            ssl_internal.scan(session)
        else:
            try:
                ssl_labs.scan(session)
            except Exception as error:
                output.debug_exception()

                output.error(f"Error running scan with SSL Labs: {str(error)}")

        if session.args.tdessessioncount:
            ssl_sweet32.scan(session)
Example #2
0
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()
Example #3
0
def start(args, url):
    print(f"Scanning: {url}")

    # parse the URL, we'll need this
    parsed = urlparse(url)
    # get rid of any port number & credentials that may exist
    domain = utils.get_domain(parsed.netloc)

    # make sure it resolves
    try:
        socket.gethostbyname(domain)
    except socket.gaierror as error:
        print(f"Fatal Error: Unable to resolve {domain} ({str(error)})")

        return

    if parsed.scheme == "http":
        try:
            # check for TLS redirect
            tls_redirect = network.check_ssl_redirect(url)
            if tls_redirect != url:
                print(f"Server redirects to TLS: Scanning: {tls_redirect}")

                url = tls_redirect
                parsed = urlparse(url)
        except Exception as error:
            output.debug_exception()
            output.error(f"Failed to connect to {url} ({str(error)})")

            return

    www_redirect = network.check_www_redirect(url)
    if www_redirect is not None and www_redirect != url:
        print(f"Server performs WWW redirect: Scanning: {www_redirect}")
        url = www_redirect
        parsed = urlparse(url)

    # check to see if we are looking at an HTTPS server
    if parsed.scheme == "https":
        if args.internalssl or utils.is_ip(domain) or utils.get_port(url) != 443:
            # use internal scanner
            ssl_internal.scan(args, url, domain)
        else:
            try:
                ssl_labs.scan(args, url, domain)
            except Exception as error:
                output.debug_exception()

                output.error(f"Error running scan with SSL Labs: {str(error)}")

        if args.tdessessioncount:
            ssl_sweet32.scan(args, url, domain)
Example #4
0
    def _get_info(self) -> str:
        # prime the call to cpu_percent, as the first call doesn't return useful data
        self.process.cpu_percent()

        # force a collection; not ideal, but seems to help
        gc.collect(2)

        # use oneshot() to cache the data, so we minimize hits
        with self.process.oneshot():
            pct = self.process.cpu_percent()

            times = self.process.cpu_times()
            mem = self.process.memory_info()
            mem_res = "{0:cM}".format(Size(mem.rss))
            mem_virt = "{0:cM}".format(Size(mem.vms))

            if mem.rss > self.peak_mem_res:
                self.peak_mem_res = mem.rss
                output.debug(f"New high-memory threshold: {self.peak_mem_res}")

            thr = self.process.num_threads()

            vm = psutil.virtual_memory()
            mem_total = "{0:cM}".format(Size(vm.total))
            mem_avail_bytes = vm.available
            mem_avail = "{0:cM}".format(Size(vm.available))

            if mem_avail_bytes < self.WARNING_THRESHOLD and not self.low_mem_warning:
                self.low_mem_warning = True

                output.error(f"Low RAM Available: {mem_avail}")

            cons = -1
            try:
                cons = len(self.process.connections(kind="inet"))
            except Exception:
                # we don't care if this fails
                output.debug_exception()

            cpu_freq = psutil.cpu_freq()

        info = (f"Process Stats: CPU: {pct}% - Sys: {times.system} - "
                f"User: {times.user} - Res: {mem_res} - Virt: {mem_virt} - "
                f"Available: {mem_avail}/{mem_total} - Threads: {thr} - "
                f"Connections: {cons} - CPU Freq: "
                f"{int(cpu_freq.current)}MHz/{int(cpu_freq.max)}MHz - "
                f"GC Objects: {len(gc.get_objects())}")

        return info
Example #5
0
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)}")
Example #6
0
    def _get_info(self) -> str:
        from yawast.external.memory_size import Size

        # prime the call to cpu_percent, as the first call doesn't return useful data
        self.process.cpu_percent(interval=1)

        # use oneshot() to cache the data, so we minimize hits
        with self.process.oneshot():
            pct = self.process.cpu_percent()

            times = self.process.cpu_times()
            mem = self._get_mem()
            mem_res = "{0:cM}".format(Size(mem.rss))
            mem_virt = "{0:cM}".format(Size(mem.vms))

            thr = self.process.num_threads()

            vm = psutil.virtual_memory()
            mem_total = "{0:cM}".format(Size(vm.total))
            mem_avail_bytes = vm.available
            mem_avail = "{0:cM}".format(Size(vm.available))

            if mem_avail_bytes < self.WARNING_THRESHOLD and not self.low_mem_warning:
                self.low_mem_warning = True

                output.error(f"Low RAM Available: {mem_avail}")

            cons = -1
            try:
                cons = len(self.process.connections(kind="inet"))
            except Exception:
                # we don't care if this fails
                output.debug_exception()

            cpu_freq = psutil.cpu_freq()

        info = (f"Process Stats: CPU: {pct}% - Sys: {times.system} - "
                f"User: {times.user} - Res: {mem_res} - Virt: {mem_virt} - "
                f"Available: {mem_avail}/{mem_total} - Threads: {thr} - "
                f"Connections: {cons} - CPU Freq: "
                f"{int(cpu_freq.current)}MHz/{int(cpu_freq.max)}MHz")

        return info
Example #7
0
def _check_password_reset(session: Session, element_name: Optional[str] = None):
    user = session.args.user
    if user is None:
        user = utils.prompt("What is a valid user? ")

    try:
        with Spinner():
            res = password_reset.check_resp_user_enum(session, user, element_name)

        if res:
            reporter.display_results(res, "\t")
    except WebDriverException as e:
        output.error("Selenium error encountered: " + e.msg)
    except PasswordResetElementNotFound as e:
        if element_name is not None:
            # we failed to find the element, and we had one specified - this isn't going to work
            output.error(
                "Unable to find a matching element to perform the User Enumeration via Password Reset: "
                + str(e)
            )
        else:
            # we failed, because we don't have the element - so we prompt for it.
            print(
                "Unable to find a known element to enter the user name. Please identify the proper element."
            )
            print(
                "If this element seems to be common, please request that it be added: https://github.com/adamcaudill/yawast/issues"
            )
            name = utils.prompt("What is the user/email entry element name? ")

            _check_password_reset(session, name)
    except Exception as e:
        output.error(
            "Failed to execute Password Reset Page User Enumeration: " + str(e)
        )
Example #8
0
def init(proxy: str, cookie: str, header: str) -> None:
    global _requester

    _requester.cookies.set_policy(_BlockCookiesSet())
    _requester.mount(
        "http://",
        HTTPAdapter(
            max_retries=urllib3.Retry(total=3, read=5, connect=5, backoff_factor=0.3),
            pool_maxsize=50,
        ),
    )
    _requester.mount(
        "https://",
        HTTPAdapter(
            max_retries=urllib3.Retry(total=3, read=5, connect=5, backoff_factor=0.3),
            pool_maxsize=50,
        ),
    )

    if proxy is not None and len(proxy) > 0:
        # we have a proxy, set it
        if not proxy.startswith("http") and "://" not in proxy:
            proxy = f"http://{proxy}"

        if proxy.startswith("http"):
            proxies = {"http": proxy, "https": proxy}

            _requester.proxies.update(proxies)
        else:
            output.error(
                f"Invalid proxy server specified ({proxy}) - only HTTP proxy servers are supported. Proxy ignored."
            )

    if cookie is not None and len(cookie) > 0:
        if "=" in cookie:
            name = cookie.split("=", 1)[0]
            val = cookie.split("=", 1)[1]
            c = requests.cookies.create_cookie(name=name, value=val)

            _requester.cookies.set_cookie(c)
        else:
            output.error(
                f"Invalid cookie specified ({cookie}) - cookie must be in NAME=VALUE format. Ignored."
            )

    if header is not None and len(header) > 0:
        if "=" in header:
            name = header.split("=", 1)[0]
            val = header.split("=", 1)[1]
            _requester.headers.update({name: val})
        elif ": " in header:
            # in case they use the wire format - not officially supported, but, meh
            name = header.split(": ", 1)[0]
            val = header.split(": ", 1)[1]
            _requester.headers.update({name: val})
        else:
            output.error(
                f"Invalid header specified ({header}) - header must be in NAME=VALUE format. Ignored."
            )
Example #9
0
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()
Example #10
0
File: scan.py Project: sgnls/yawast
def start(session: Session):
    print(f"Scanning: {session.url}")

    # make sure it resolves
    try:
        socket.gethostbyname(session.domain)
    except socket.gaierror as error:
        output.debug_exception()
        output.error(
            f"Fatal Error: Unable to resolve {session.domain} ({str(error)})")

        return

    try:
        cutils.check_redirect(session)
    except Exception as error:
        output.debug_exception()
        output.error(f"Unable to continue: {str(error)}")

        return

    if not session.args.nodns:
        dns.scan(session)

    if session.args.ports:
        network.scan(session)

    # check to see if we are looking at an HTTPS server
    if session.url_parsed.scheme == "https" and not session.args.nossl:
        if (session.args.internalssl or utils.is_ip(session.domain)
                or utils.get_port(session.url) != 443):
            # use internal scanner
            ssl_internal.scan(session)
        else:
            try:
                ssl_labs.scan(session)
            except Exception as error:
                output.debug_exception()

                output.error(f"Error running scan with SSL Labs: {str(error)}")
                output.norm("Switching to internal SSL scanner...")

                ssl_internal.scan(session)

        if session.args.tdessessioncount:
            ssl_sweet32.scan(session)

    http.scan(session)

    # reset any stored data
    http.reset()

    return
Example #11
0
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()
Example #12
0
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()
Example #13
0
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")
Example #14
0
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")
Example #15
0
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
Example #16
0
def start(args, url):
    print(f"Scanning: {url}")

    # parse the URL, we'll need this
    parsed = urlparse(url)
    # get rid of any port number & credentials that may exist
    domain = utils.get_domain(parsed.netloc)

    # make sure it resolves
    try:
        socket.gethostbyname(domain)
    except socket.gaierror as error:
        print(f"Fatal Error: Unable to resolve {domain} ({str(error)})")

        return

    # perform some connection testing
    if parsed.scheme == "http":
        try:
            # check for TLS redirect
            tls_redirect = network.check_ssl_redirect(url)
            if tls_redirect != url:
                print(f"Server redirects to TLS: Scanning: {tls_redirect}")

                url = tls_redirect
                parsed = urlparse(url)
        except Exception:
            output.debug_exception()

            # we tried to connect to port 80, and it failed
            # this could mean a couple things, first, we need to
            #  see if it answers to 443
            parsed = parsed._replace(scheme="https")
            url = urlunparse(parsed)

            print("Server does not respond to HTTP, switching to HTTPS")
            print()
            print(f"Scanning: {url}")

            # grab the head, to see if we get anything
            try:
                network.http_head(url, timeout=5)

                print()
            except Exception as err:
                output.debug_exception()

                print(f"Fatal Error: Can not connect to {url} ({str(err)})")
                return
    else:
        # if we are scanning HTTPS, try HTTP to see what it does
        try:
            http_parsed = parsed._replace(scheme="http")
            http_url = urlunparse(http_parsed)

            network.http_head(http_url, timeout=5)

            print("Server responds to HTTP requests")
            print()
        except Exception:
            output.debug_exception()

            print("Server does not respond to HTTP requests")
            print()

    # check for www redirect
    www_redirect = network.check_www_redirect(url)
    if www_redirect is not None and www_redirect != url:
        print(f"Server performs WWW redirect: Scanning: {www_redirect}")
        url = www_redirect

    if not args.nodns:
        dns.scan(args, url, domain)

    # check to see if we are looking at an HTTPS server
    if parsed.scheme == "https" and not args.nossl:
        if args.internalssl or utils.is_ip(
                domain) or utils.get_port(url) != 443:
            # use internal scanner
            ssl_internal.scan(args, url, domain)
        else:
            try:
                ssl_labs.scan(args, url, domain)
            except Exception as error:
                output.debug_exception()

                output.error(f"Error running scan with SSL Labs: {str(error)}")

        if args.tdessessioncount:
            ssl_sweet32.scan(args, url, domain)

    http.scan(args, url, domain)

    # reset any stored data
    http.reset()

    return
Example #17
0
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()
Example #18
0
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']}")
Example #19
0
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()
Example #20
0
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()
Example #21
0
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")