SSL Labs

Analyze a host or a URL.

Vulnerability Management · SSL Labs

Details

IDSSL Labs
ProviderQualys
CategoryVulnerability Management
From Version6.0.0
Docker Imagedemisto/python3:3.12.8.3296088
Supported ModulesAgentix XSIAM

README

Analyze a host or a URL.

Configure SSL Labs (Community Contribution) in Cortex

Parameter Description Required
Registered Email Address The registered email address that will be used to access SSL Labs. False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

ssl-labs-register-email


Register for Scan API initiation and result fetching

Base Command

ssl-labs-register-email

Input

Argument Name Description Required
firstName Users First Name. Required
lastName Users Last Name. Required
email Users Email Address. Email services such as Gmail, Yahoo, or Hotmail are not allowed. Required
organization Name of the organization using the service. Required

Context Output

Path Type Description
SslLabs.Registation.message string Registration message response
SslLabs.Registation.status string Either success or failure

ssl-labs-info


Check the availability of the SSL Labs servers, retrieve the engine and criteria version, and initialize the maximum number of concurrent assessments.

Base Command

ssl-labs-info

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SslLabs.Info.criteriaVersion string Rating criteria version as a string (e.g., “2009f”)
SslLabs.Info.currentAssessments number The number of ongoing assessments submitted by this client.
SslLabs.Info.engineVersion string SSL Labs software version as a string (e.g., “2.2.0”)
SslLabs.Info.maxAssessments number The maximum number of concurrent assessments the client is allowed to initiate.
SslLabs.Info.newAssessmentCoolOff number The cool-off period after each new assessment, in milliseconds; you’re not allowed to submit a new assessment before the cool-off expires, otherwise you’ll get a 429.
SslLabs.Info.messages string A list of messages (strings). Messages can be public (sent to everyone) and private (sent only to the invoking client). Private messages are prefixed with “[Private]”

ssl-labs-analyze


Invoke assessments.

Base Command

ssl-labs-analyze

Input

Argument Name Description Required
host Provide hostname or URL. Required
publish Set to on if assessment results needs to be published on the public results boards. Default: off. Possible values are: off, on. Default is off. Optional
startNew If on setting is enabled, a new assessment is started, even if there is a cached assessment in progress. However, if an assessment is in progress, its status is returned instead of starting a new assessment. Note: This parameter should only be used once to start a new assessment; any additional use may cause an assessment loop. Possible values are: off, on. Default is off. Optional
fromCache Delivers cached assessment reports if available. This parameter is intended for API consumers who do not wish to wait for assessment results and cannot be used simultaneously with the startNew parameter. Default: off. Possible values are: off, on. Default is off. Optional
maxAge Maximum report age in hours if retrieving from cache (fromCache parameter). Optional
all When the parameter is set to on, full information will be returned. When the parameter is set to done, full information will be returned only if the assessment is complete (status is READY or ERROR). Possible values are: off, on. Default is on. Optional
ignoreMismatch Ignores the mismatch if server certificate doesn’t match the assessment hostname and proceeds with assessments if set to on. Default: off Note: This parameter is ignored if a cached report is returned. Possible values are: off, on. Default is off. Optional

Context Output

Path Type Description
SslLabs.Analyze.host string Assessment host, which can be a hostname or an IP address
SslLabs.Analyze.port number Assessment port (e.g., 443)
SslLabs.Analyze.protocol string Protocol (e.g., HTTP)
SslLabs.Analyze.isPublic boolean true if this assessment is publicly available (listed on the SSL Labs assessment boards)
SslLabs.Analyze.status string Assessment status; possible values: DNS, ERROR, IN_PROGRESS, and READY.
SslLabs.Analyze.startTime number Assessment starting time, in milliseconds since 1970
SslLabs.Analyze.testTime number Assessment completion time, in milliseconds since 1970
SslLabs.Analyze.engineVersion string Assessment engine version (e.g., “2.2.0”)
SslLabs.Analyze.criteriaVersion string Grading criteria version (e.g., “2009l”)
SslLabs.Analyze.cacheExpiryTime number When will the assessment results expire from the cache (typically set only for assessment with errors; otherwise the results stay in the cache for as long as there’s sufficient room)
SslLabs.Analyze.certHostnames unknown The list of certificate hostnames collected from the certificates seen during assessment. The hostnames may not be valid. This field is available only if the server certificate doesn’t match the requested hostname. In that case, this field saves you some time as you don’t have to inspect the certificates yourself to find out what valid hostnames might be.
SslLabs.Analyze.endpoints unknown list of Endpoint objects
SslLabs.Analyze.certs unknown a list of Cert object, representing the chain certificates in the order in which they were retrieved from the server.

Configuration parameters

  • email — Registered Email Address

Commands (3)

  • ssl-labs-analyze

    Invoke assessments.

  • ssl-labs-info

    Check the availability of the SSL Labs servers, retrieve the engine and criteria version, and initialize the maximum number of concurrent assessments.

  • ssl-labs-register-email

    Register for Scan API initiation and result fetching.

import re

import urllib3
from CommonServerPython import *  # noqa: F401

# Disable insecure warnings
urllib3.disable_warnings()


class Client(BaseClient):
    def __init__(self, base_url: str, proxy: bool, verify: bool, headers: dict):
        """
        Client to use. Overrides BaseClient.

        Args:
            base_url (str): URL to access when doing a http request. Webhook url.

        """
        super().__init__(base_url=base_url, proxy=proxy, verify=verify, headers=headers)

    def register(self, first_name: str, last_name: str, email: str, org: str):
        """
        Registers the user of the API Service.

        Args:
            first_name (str): The users first name used for registering to the API service
            last_name (str): The users last name used for registering to the API service
            email (str): The users email used for registering to the API service
            org (str): The organization registering to the API service
        """

        json_data = {"firstName": first_name, "lastName": last_name, "email": email, "organization": org}

        res = self._http_request(
            method="POST",
            json_data=json_data,
            raise_on_status=True,
            url_suffix="/register",
            headers={"Content-Type": "application/json"},
        )
        demisto.info(f"Registration Sent. Response {res}")
        return res

    def info(self):
        """
        Check the availability of the SSL Labs servers
        retrieve the engine and criteria version
        initialize the maximum number of concurrent assessments.

        Args:
            None
        """

        res = self._http_request(
            method="GET", raise_on_status=True, url_suffix="/info", headers={"Content-Type": "application/json"}
        )
        demisto.info(f"SSL Labs Info. Response {res}")
        return res

    def analyze(
        self,
        host: str,
        publish: Optional[str],
        start_new: Optional[str],
        from_cache: Optional[str],
        max_age: Optional[str],
        all_endpoints: Optional[str],
        ignore_mismatch: Optional[str],
    ):
        """
            Initiate an assessment, or to retrieve the status of an assessment in
            progress or in the cache. It will return a single Host object on success.
            The Endpoint object embedded in the Host object will provide partial endpoint results.

        Args:
            client (Client): SSL Labs client to use.
            host (str): Hostname or URL to analyze.
            publish (str): Set to on if assessment results needs to be published on the public results
                boards.
            start_new (str): If on setting is enabled, a new assessment is started, even if there is a
                cached assessment in progress. However, if an assessment is in progress, its status is
                returned instead of starting a new assessment. Note: This parameter should only be used
                once to start a new assessment; any additional use may cause an assessment loop.
            from_cache (str): Delivers cached assessment reports if available. This parameter is
                intended for API consumers who do not wish to wait for assessment results and cannot be
                used simultaneouslywith the startNew parameter.
            max_age (str): Maximum report age in hours if retrieving from cache (fromCache parameter).
            all_endpoints (str): When the parameter is set to on, full information will be returned.
                When the parameter is set to done, full information will be returned only if the assessment
                is complete (status is READY or ERROR).
            ignore_mismatch (str): Ignores the mismatch if server certificate doesn't match the assessment
                hostname and proceeds with assessments if set to on.
        """
        cmd = "ssl-labs-analyze"
        ScheduledCommand.raise_error_if_not_supported()
        polling_timeout = 600
        interval_in_secs = 60
        url_suffix = (
            f"/analyze?host={host}&publish={publish}&startNew={start_new}"
            f"&fromCache={from_cache}&maxAge={max_age}&all={all_endpoints}"
            f"&ignoreMismatch={ignore_mismatch}"
        )
        res = self._http_request(method="GET", raise_on_status=True, url_suffix=url_suffix)
        outputs = []
        polling_args = {
            "host": host,
            "publish": publish,
            "start_new": start_new,
            "from_cache": from_cache,
            "max_age": max_age,
            "all_endpoints": all_endpoints,
            "ignore_mismatch": ignore_mismatch,
            "interval_in_seconds": interval_in_secs,
            "polling": True,
        }
        if res["status"] != "READY":
            status = res["status"]
            scheduled_command = ScheduledCommand(
                command=cmd, next_run_in_seconds=interval_in_secs, args=polling_args, timeout_in_seconds=polling_timeout
            )
            command_results = CommandResults(scheduled_command=scheduled_command, readable_output=f"Scan Status: {status}")
            return command_results
        else:
            outputs.append(
                {
                    "host": res.get("host"),
                    "port": res.get("port"),
                    "protocol": res.get("protocol"),
                    "status": res.get("status"),
                    "start_time": res.get("startTime"),
                    "test_time": res.get("testTime"),
                }
            )
            headers = ["host", "port", "protocol", "status", "start_time", "test_time"]
        return CommandResults(
            outputs_prefix="sslLabs.analysis",
            outputs=outputs,
            readable_output=tableToMarkdown("SSL Labs Analysis", outputs, headers, removeNull=True),
            raw_response=res,
        )

    def is_valid(self, host: str):
        """
        This regex checks for the basic components of a URL
        """
        regex = re.compile(
            r"^(?:http|ftp)s?://"  # http:// or https://
            r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"  # subdomain
            r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"  # top-level domain
            r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"  # ...or ip
            r"(?::\d+)?"  # optional port
            r"(?:/?|[/?]\S+)$",
            re.IGNORECASE,
        )
        return re.match(regex, host) is not None


def register_email_command(client: Client, first_name: str, last_name: str, email: str, org: str) -> CommandResults:
    """
    SSLLabs has been available directly for all its users directly
    via UI and API to be consumed freely. It will remain the same with
    slight change with introduction to this new registration API. Now
    you need to register yourself with first name, last name,
    organization's name and organization's email.

    Args:
        client (Client): SSL Labs client to use.
        first_name (str): The users first name used for registering
        to the API service
        last_name (str): The users last name used for registering to
        the API service
        email (str): The users email used for registering to the
        API service
        org (str): The organization registering to the API service

    Returns:
        CommandResults/dict: A 'CommandResults' Compatible to return
        'return_results()', which contains the readable_output
        indicating the message was sent.
    """
    res = client.register(first_name, last_name, email, org)
    if res:
        result = {"message": res.get("message"), "status": res.get("status")}
        markdown = "### SSL Labs Registration\n"
        markdown += tableToMarkdown("Response", result)
        results = CommandResults(
            readable_output=markdown, outputs_prefix="SslLabs.Registation", outputs_key_field="name", outputs=result
        )
        return results
    return CommandResults(entry_type=EntryType.ERROR, readable_output="Could not register email")


def info_command(client: Client) -> CommandResults:
    """
    This API request should be used to check the availability of
    the SSL Labs servers, retrieve the engine and criteria version,
    and initialize the maximum number of concurrent assessments.

    Args:
        client (Client): SSL Labs client to use.

    Returns:
        CommandResults/dict: A 'CommandResults' Compatible to return
        'return_results()', which contains the readable_output
        indicating the message was sent.
    """
    res = client.info()
    if res:
        result = {
            "engineVersion": res.get("engineVersion"),
            "criteriaVersion": res.get("criteriaVersion"),
            "maxAssessments": res.get("maxAssessments"),
            "currentAssessments": res.get("currentAssessments"),
            "newAssessmentCoolOff": res.get("newAssessmentCoolOff"),
            "messages": res.get("messages"),
        }
        markdown = "### SSL Labs Info\n"
        markdown += tableToMarkdown("Response", result)
        results = CommandResults(
            readable_output=markdown, outputs_prefix="SslLabs.Info", outputs_key_field="name", outputs=result
        )
        return results
    return CommandResults(entry_type=EntryType.ERROR, readable_output="Could not retrieve client info")


def analyze_command(
    client: Client,
    host: str,
    publish: Optional[str],
    start_new: Optional[str],
    from_cache: Optional[str],
    max_age: Optional[str],
    all_endpoints: Optional[str],
    ignore_mismatch: Optional[str],
) -> CommandResults:
    """
    This API request is used to initiate an assessment, or to retrieve the status of an assessment
    in progress or in the cache. It will return a single Host object on success. The Endpoint
    object embedded in the Host object will provide partial endpoint results. Please note that
    assessments of individual endpoints can fail even when the overall assessment is successful
    (e.g., one server might be down). At this time, you can determine the success of an endpoint
    assessment by checking the statusMessage field; it should contain "Ready".

    Args:
        client (Client): SSL Labs client to use.
        host (str): Hostname or URL to analyze.
        publish (str): Set to on if assessment results needs to be published on the public results
            boards.
        start_new (str): If on setting is enabled, a new assessment is started, even if there is a
            cached assessment in progress. However, if an assessment is in progress, its status is
            returned instead of starting a new assessment. Note: This parameter should only be used
            once to start a new assessment; any additional use may cause an assessment loop.
        from_cache (str): Delivers cached assessment reports if available. This parameter is intended
            for API consumers who do not wish to wait for assessment results and cannot be used simultaneously
            with the startNew parameter.
        max_age (str): Maximum report age in hours if retrieving from cache (fromCache parameter).
        all_endpoints (str): When the parameter is set to on, full information will be returned.
            When the parameter is set to done, full information will be returned only if the assessment
            is complete (status is READY or ERROR).
        ignore_mismatch (str): Ignores the mismatch if server certificate doesn't match the assessment
            hostname and proceeds with assessments if set to on.

    Returns:
        CommandResults/dict: A 'CommandResults' Compatible to return 'return_results()',
        which contains the readable_output indicating the message was sent.
    """

    is_url = client.is_valid(host)
    if is_url is False:
        raise Exception(f"Input is not a valid URL.\nhttp://example.com OR https://example.com \nInput provided {host}")

    res = client.analyze(host, publish, start_new, from_cache, max_age, all_endpoints, ignore_mismatch)
    return res


def test_module(client):
    """
    Test command, will send a request to the info endpoint.

    Args:
        client (Client): SSL Labs client to use.

    Return:
        str: 'ok' if test passed, anything else will raise an exception in main.
    """
    res = client.info()
    if res:
        return "ok"
    return None


""" ENTRY POINT """


def main() -> None:
    """
    Main function, parses params and runs command functions
    Executes a test, analyzes hosts and urls, gets info
    """

    params = demisto.params()
    args = demisto.args()
    command = demisto.command()

    email = params.get("email")
    base_url = "https://api.ssllabs.com/api/v4"
    proxy = params.get("proxy", False)
    verify_certificate = not params.get("insecure", False)
    headers = {"email": email}

    try:
        client = Client(base_url=base_url, verify=verify_certificate, proxy=proxy, headers=headers)

        # Runs the Register Email command.
        if command == "ssl-labs-register-email":
            first_name = args.get("firstName", "")
            last_name = args.get("lastName", "")
            email = args.get("email", "")
            org = args.get("organization", "")
            return_results(register_email_command(client, first_name, last_name, email, org))
        # Runs the Info command.
        elif command == "ssl-labs-info":
            return_results(info_command(client))
        # Runs the Analyze command.
        elif command == "ssl-labs-analyze":
            host = args.get("host", "")
            publish = args.get("publish", "")
            start_new = args.get("startNew", "")
            from_cache = args.get("fromCache", "")
            max_age = args.get("maxAge", "")
            all_endpoints = args.get("all", "")
            ignore_mismatch = args.get("ignoreMismatch", "")
            return_results(analyze_command(client, host, publish, start_new, from_cache, max_age, all_endpoints, ignore_mismatch))
        elif command == "test-module":
            return_results(test_module(client))
        else:
            raise NotImplementedError(f"command {command} is not implemented.")

    except Exception as e:
        demisto.error(traceback.format_exc())
        return_error(f"Failed to execute {command} encountered {e}.")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()