URLSSLVerification

Verify URL SSL certificate.

python · Common Scripts

Details

IDURLSSLVerification
Languagepython
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10404775
Tagsurl Enrichment

README

Verifies the URL’s/URL redirect chain for SSL certificate.

Script Data


Name Description
Script Type python
Tags url, Enrichment

Inputs


Argument Name Description
url Comma separated list of URLs to verify.

Outputs


Path Description Type
URL The URL object. Unknown
URL.Data The URL address. string
URL.Malicious The malicious description. Unknown
DBotScore The DBotScore object. Unknown
DBotScore.Indicator The indicator. string
DBotScore.Type The indicator’s type. string
DBotScore.Vendor The reputation vendor. string
DBotScore.Score The reputation score. number
import pytest
from pytest_mock import MockerFixture
import demistomock as demisto
from URLSSLVerification import arg_to_list_with_regex, mark_http_as_suspicious, main, verify_ssl_certificate
import requests
from requests.exceptions import SSLError


@pytest.mark.parametrize("arg, expected_result", [("false", False), ("true", True), (None, True)])
def test_is_http_should_be_suspicious(arg, expected_result):
    assert mark_http_as_suspicious(arg) == expected_result


@pytest.mark.parametrize(
    "arg, expected_result",
    [
        (None, []),
        (["some_url"], ["some_url"]),
        ('["some_url"]', ["some_url"]),
        ("https://some_url.com", ["https://some_url.com"]),
    ],
)
def test_arg_to_list_with_regex(arg, expected_result):
    assert arg_to_list_with_regex(arg) == expected_result


def create_response(url, status_code=200, history=[]):
    resp = requests.Response()
    resp.status_code = status_code
    resp.url = url
    resp.history = history
    return resp


def fake_requests_get(url, timeout=1, allow_redirects=True, verify=True):
    """
    This helper function simulates HTTP requests with no real URL.
    It fabricates response objects that include status codes, URL redirection history, and header-like information.

    Args:
        url (str): The target URL.
        timeout (int or float, optional): Maximum time in seconds to wait for the response.
        allow_redirects (bool, optional): Whether to follow redirects. Defaults to True.
        verify (bool, optional): Whether to enforce SSL certificate verification. Defaults to True.

    Raises:
        SSLError: If SSL certificate verification fails.

    Returns:
        requests.Response: A simulated HTTP response including status, final URL, and redirect history.

    """
    if url == "https_no_certificate":
        raise SSLError("SSL certificate error")

    elif url == "http" or url == "https_certificate":
        return create_response(url=url)

    elif url == "http_to_https_certificate":
        first_response = create_response(url="http_to_https_certificate", status_code=301)
        if allow_redirects:
            return create_response(url="https_certificate", history=[first_response])
        else:
            return first_response
    elif url == "http_to_http":
        first_response = create_response(url="http_to_http", status_code=301)
        if allow_redirects:
            return create_response(url="http", history=[first_response])
        else:
            return first_response
    raise requests.exceptions.RequestException


@pytest.mark.parametrize(
    "arg, expected_results",
    [
        # Set_http_as_suspicious = True
        ({"url": "http", "set_http_as_suspicious": "true"}, [{"Verified": False, "Score": 2, "Indicator": "http"}]),
        (
            {"url": "https_certificate", "set_http_as_suspicious": "true"},
            [{"Verified": True, "Score": 0, "Indicator": "https_certificate"}],
        ),
        # Set_http_as_suspicious = False
        (
            {"url": "http_to_https_certificate", "set_http_as_suspicious": "false"},
            [{"Verified": True, "Score": 0, "Indicator": "http_to_https_certificate"}],
        ),
        ({"url": "http", "set_http_as_suspicious": "false"}, [{"Verified": False, "Score": 2, "Indicator": "http"}]),
        (
            {"url": "https_certificate", "set_http_as_suspicious": "false"},
            [{"Verified": True, "Score": 0, "Indicator": "https_certificate"}],
        ),
        (
            {"url": "https_no_certificate", "set_http_as_suspicious": "false"},
            [{"Verified": False, "Score": 2, "Indicator": "https_no_certificate"}],
        ),
        (
            {"url": "http_to_http", "set_http_as_suspicious": "false"},
            [{"Verified": False, "Score": 2, "Indicator": "http_to_http"}],
        ),
        (
            {"url": "invalid_url", "set_http_as_suspicious": "false"},
            [{"Verified": False, "Score": 2, "Indicator": "invalid_url"}],
        ),
        # Test Multiple url
        (
            {"url": ["invalid_url", "http_to_http", "http_to_https_certificate"], "set_http_as_suspicious": "false"},
            [
                {"Verified": False, "Score": 2, "Indicator": "invalid_url"},
                {"Verified": False, "Score": 2, "Indicator": "http_to_http"},
                {"Verified": True, "Score": 0, "Indicator": "http_to_https_certificate"},
            ],
        ),
    ],
)
def test_main(arg, expected_results, mocker: MockerFixture):
    """
    Given:
        Requests.get work as mentioned above.
    When:
        main function is called.
    Then:
        The function should return the appropriate message.
    """
    # Mock demisto.args()
    mocker.patch.object(
        demisto,
        "args",
        return_value=arg,
    )
    # Mock return_results
    mock_return_results = mocker.patch("URLSSLVerification.return_results")
    mocker.patch("URLSSLVerification.requests.get", side_effect=fake_requests_get)

    # Call the main function
    main()

    # Assert that return_results was called with the correct arguments
    mock_return_results.assert_called_once()

    result = mock_return_results.call_args[0][0]

    for url in result.outputs["URL"]:
        assert url["Verified"] == [ret["Verified"] for ret in expected_results if ret["Indicator"] == url["Data"]][0]

    for d_bot in result.outputs["DBotScore"]:
        assert d_bot["Score"] == [ret["Score"] for ret in expected_results if ret["Indicator"] == d_bot["Indicator"]][0]


@pytest.mark.parametrize(
    "url, description_result",
    [
        ("https_no_certificate", "SSL Certificate verification failed"),
        ("invalid_url", "Failed to establish a new connection with the URL"),
    ],
)
def test_verify_ssl_certificate(url, description_result, mocker: MockerFixture):
    """
    Given:
        Requests.get work as mentioned above.
    When:
        verify_ssl_certificate is called.
    Then:
        The function should return the appropriate message.
    """
    mocker.patch("URLSSLVerification.requests.get", side_effect=fake_requests_get)

    result = verify_ssl_certificate(url)
    assert result
    assert result["Description"] == description_result