QintelQWatch

Qintel's QWatch system contains credentials obtained from dump sites, hacker collaboratives, and command and control infrastructures of eCrime- and APT-related malware. With this integration, users can fetch exposure alerts as incidents and discover exposed credentials associated with their organization.

Data Enrichment & Threat Intelligence · Qintel

Details

IDQintelQWatch
ProviderQintel
CategoryData Enrichment & Threat Intelligence
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Qintel’s QWatch system contains credentials obtained from dump sites, hacker collaboratives, and command and control infrastructures of eCrime- and APT-related malware. With this integration, users can fetch exposure alerts as incidents and discover exposed credentials associated with their organization.
This integration was integrated and tested with version 1.1.6 of QWatch

Configure QintelQWatch in Cortex

Parameter Required
QWatch API URL (optional) False
Qintel Credentials True
Password True
Trust any certificate (not secure) False
Use system proxy settings False
Fetch incidents False
Fetch plaintext passwords False
Limit number of records per fetch False
First fetch time False
Incidents Fetch Interval False
Default Incident Severity True

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.

qintel-qwatch-exposures


Search QWatch for exposed credentials

Base Command

qintel-qwatch-exposures

Input

Argument Name Description Required
email Email to search. Optional
domain Domain to search. Optional

Context Output

Path Type Description
Qintel.QWatch.Exposures String QWatch Exposure Records

Command Example

!qintel-qwatch-exposures email=test@example.local

Context Example

{
    "Qintel": {
        "QWatch": {
            "Exposures": [
                {
                    "email": "test@example.local",
                    "firstseen": "2020-03-25 09:38:40",
                    "lastseen": "2021-02-05 04:35:33",
                    "loaded": "2021-02-05 04:35:33",
                    "password": "SuperSecretPassword",
                    "source": "combo-BigComboList"
                },
                {
                    "email": "test@example.local",
                    "firstseen": "2020-03-25 09:38:40",
                    "lastseen": "2021-02-05 04:35:33",
                    "loaded": "2020-08-10 02:10:11",
                    "password": "SuperSecretPassword",
                    "source": "dump-example.local"
                },
                {
                    "email": "test@example.local",
                    "firstseen": "2020-03-25 09:38:40",
                    "lastseen": "2021-02-05 04:35:33",
                    "loaded": "2020-03-25 09:38:40",
                    "password": "SuperSecretPassword",
                    "source": "malware-evilbot_March_22_2020"
                }
            ]
        }
    }
}

Human Readable Output

Qintel QWatch exposures for: test@example.local

Email Password Source Loaded First Seen Last Seen
test@example.local SuperSecretPassword combo-BigComboList 2021-02-05 04:35:33 2020-03-25 09:38:40 2021-02-05 04:35:33
test@example.local SuperSecretPassword dump-example.local 2020-08-10 02:10:11 2020-03-25 09:38:40 2021-02-05 04:35:33
test@example.local SuperSecretPassword malware-evilbot_March_22_2020 2020-03-25 09:38:40 2020-03-25 09:38:40 2021-02-05 04:35:33

Configuration parameters

  • remote — QWatch API URL (optional)
  • credentials — Qintel Credentials (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • fetch_passwords — Fetch plaintext passwords
  • max_fetch — Limit number of records per fetch
  • first_fetch — First fetch time
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_severity — Default Incident Severity (required)
  • incidentType — Incident type

Commands (1)

  • qintel-qwatch-exposures

    Search QWatch for exposed credentials.

"""Qintel QWatch Integration for Cortex XSOAR - Unit Tests file"""

import json

MOCK_URL = "https://this-is-only-a-test.local"
MOCK_CLIENT_ID = "client-id"
MOCK_CLIENT_SECRET = "client-secret"


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


def test_make_timestamp():
    from datetime import datetime

    from QintelQWatch import _make_timestamp

    res = _make_timestamp(None)
    assert res is None

    res = _make_timestamp(1626211855)
    assert isinstance(res, datetime)
    assert res.isoformat() == "2021-07-13T21:30:55"

    res = _make_timestamp("2021-07-13T21:30:55")
    assert isinstance(res, datetime)
    assert int(res.timestamp()) == 1626211855


def test_test_module(mocker):
    """Tests test-module command function with valid response.

    Checks the output of the command function with the expected output.
    """

    from QintelQWatch import Client, test_module

    client = Client(base_url=MOCK_URL, verify=False, client_id=MOCK_CLIENT_ID, client_secret=MOCK_CLIENT_SECRET)

    mock_response = util_load_json("test_data/test_module.json")
    mocker.patch.object(Client, "_http_request", return_value=mock_response)

    response = test_module(client)

    assert response == "ok"


def test_fetch_incidents_command(mocker):
    """Tests fetch-incidents command function with valid response.

    Checks the output of the command function with the expected output when
    plaintext passwords ARE requested.
    """

    from QintelQWatch import Client, fetch_incidents

    client = Client(base_url=MOCK_URL, verify=False, client_id=MOCK_CLIENT_ID, client_secret=MOCK_CLIENT_SECRET)

    mock_response = util_load_json("test_data/qwatch_data.json")
    mocker.patch.object(Client, "_http_request", return_value=mock_response)

    params = {"first_fetch": "90 days", "fetch_severity": "Medium", "fetch_passwords": True}

    response = fetch_incidents(client, params)

    assert len(response) == 1

    rawJSON = json.loads(response[0]["rawJSON"])

    assert len(rawJSON["QWatch"]["Exposures"]) == 3

    record = rawJSON["QWatch"]["Exposures"][0]

    assert record["email"] == "test@example.local"
    assert record["password"] == "SuperSecretPassword"
    assert record["source"] == "combo-BigComboList"
    assert record["loaded"] == "2021-02-05 04:35:33"


def test_fetch_incidents_command_no_password(mocker):
    """Tests fetch-incidents command function with valid response.

    Checks the output of the command function with the expected output when
    plaintext passwords are NOT requested.
    """

    from QintelQWatch import Client, fetch_incidents

    client = Client(base_url=MOCK_URL, verify=False, client_id=MOCK_CLIENT_ID, client_secret=MOCK_CLIENT_SECRET)

    mock_response = util_load_json("test_data/qwatch_data.json")
    mocker.patch.object(Client, "_http_request", return_value=mock_response)

    params = {"first_fetch": "90 days", "fetch_severity": "Medium"}

    response = fetch_incidents(client, params)

    assert len(response) == 1

    rawJSON = json.loads(response[0]["rawJSON"])

    assert len(rawJSON["QWatch"]["Exposures"]) == 3

    record = rawJSON["QWatch"]["Exposures"][0]

    assert record["email"] == "test@example.local"
    assert record["password"] is None
    assert record["source"] == "combo-BigComboList"
    assert record["loaded"] == "2021-02-05 04:35:33"


def test_fetch_incidents_command_no_results(mocker):
    """Tests fetch-incidents command function with valid response.

    Checks the output of the command function with the expected output when
    no results are returned.
    """

    from QintelQWatch import Client, fetch_incidents

    client = Client(base_url=MOCK_URL, verify=False, client_id=MOCK_CLIENT_ID, client_secret=MOCK_CLIENT_SECRET)

    mock_response = util_load_json("test_data/qwatch_data_empty.json")
    mocker.patch.object(Client, "_http_request", return_value=mock_response)

    params = {"first_fetch": "90 days", "fetch_severity": "Medium"}

    response = fetch_incidents(client, params)

    assert len(response) == 0


def test_search_exposures(mocker):
    """Tests qintel-qwatch-exposures command function with valid response.

    Checks the output of the command function with the expected output
    """

    from QintelQWatch import Client, search_exposures

    client = Client(base_url=MOCK_URL, verify=False, client_id=MOCK_CLIENT_ID, client_secret=MOCK_CLIENT_SECRET)

    mock_response = util_load_json("test_data/qwatch_data.json")
    mocker.patch.object(Client, "_http_request", return_value=mock_response)

    args = {
        "email": "test@example.local",
    }

    params = {"fetch_passwords": True}

    response = search_exposures(client, args, params)

    assert len(response) == 1

    outputs = response[0].outputs
    hr = response[0].readable_output
    prefix = response[0].outputs_prefix

    assert prefix == "Qintel"

    assert "Qintel QWatch exposures for: test@example.local" in hr
    assert "|Email|Password|Source|Loaded|First Seen|Last Seen|" in hr
    assert "test@example.local | SuperSecretPassword | malware-evilbot_March_22_2020 | 2020-03-25 09:38:40 |" in hr

    record = outputs["QWatch"]["Exposures"][0]

    assert record["email"] == "test@example.local"
    assert record["password"] == "SuperSecretPassword"
    assert record["source"] == "combo-BigComboList"
    assert record["loaded"] == "2021-02-05 04:35:33"