ReliaquestTakedown

This is Reliaquest DRP Takedown integration. It enables xsoar user to create and manage takedowns.

Vulnerability Management · ReliaQuest Digital Risk Protection

Details

IDReliaquestTakedown
ProviderReliaQuest
CategoryVulnerability Management
From Version6.9.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This is Reliaquest DRP Takedown integration. It enables xsoar user to create and manage takedowns.
This integration was integrated and tested with version 6.9.0 of ReliaquestTakedown.

This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.

Configure ReliaquestTakedown in Cortex

Parameter Description Required
DS SearchLight API URL Enter the Digital Shadows SearchLight API URL. True
Account ID Account ID associated with this account. True
API Key Enter the API Key for this account. True
API Secret Enter the API Secret for this account. True
Trust any certificate (not secure) Verify certificate. False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 2 months, 1 years or datetime in “%Y-%m-%d %H:%M:%S” format) First fetch False
Fetch Limit The maximum number of takedown to fetch. True
Takedown This controls how often the integration will perform a fetch takwdown command. False
Incident type   False
Fetch incidents   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.

rq-takedown-create


Create takedown command takes brand id, type, target and portal shortcode (optional) and returns the created takedown in response.

Base Command

rq-takedown-create

Input

Argument Name Description Required
brand_id Brand Id. Required
type Takedown Type. Required
target Target URL. Required
portal_id Portal shortcode. Optional

Context Output

There is no context output for this command.

rq-takedown-list-brand


Returns list of allowed brand details for takedown.

Base Command

rq-takedown-list-brand

Input

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

Context Output

There is no context output for this command.

rq-takedown-create-comment


Create comment for a takedown.

Base Command

rq-takedown-create-comment

Input

Argument Name Description Required
comment Comment for takedown. Default is Investigate the tekedown. Required
takedown_id Takedown id. Default is UUID. Required

Context Output

There is no context output for this command.

rq-takedown-upload-attachment


Uploads attachment for takedown.

Base Command

rq-takedown-upload-attachment

Input

Argument Name Description Required
file_id No description provided. Required
takedown_id No description provided. Required

Context Output

There is no context output for this command.

rq-takedown-download-attachment


Downloads attachment for takedown.

Base Command

rq-takedown-download-attachment

Input

Argument Name Description Required
attachment_id No description provided. Required

Context Output

There is no context output for this command.

Incident Mirroring

You can enable incident mirroring between Cortex XSOAR incidents and ReliaquestTakedown corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:

  1. Enable Fetching incidents in your instance configuration.

Newly fetched incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
Important Note: To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and ReliaquestTakedown.

Configuration parameters

  • searchLightUrl — DS SearchLight API URL (required)
  • accountId — Account ID (required)
  • apiKey — (required)
  • apiSecret — (required)
  • insecure — Trust any certificate (not secure)
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 2 months, 1 years or datetime in "%Y-%m-%d %H:%M:%S" format)
  • max_fetch — Fetch Limit (required)
  • incidentFetchInterval — Takedown
  • incidentType — Incident type
  • isFetch — Fetch incidents

Commands (5)

  • rq-takedown-create

    Create takedown command takes brand id, type, target and portal shortcode (optional) and returns the created takedown in response.

  • rq-takedown-create-comment

    Create comment for a takedown.

  • rq-takedown-download-attachment

    Downloads attachment for takedown.

  • rq-takedown-list-brand

    Returns list of allowed brand details for takedown.

  • rq-takedown-upload-attachment

    Uploads attachment for takedown.

import json
import random
import re
import uuid

import pytest
import requests

from Packs.DigitalShadows.Integrations.ReliaquestTakedown.ReliaquestTakedown import (
    create_comment,
    list_brands,
    create_takedown,
    download_attachment,
    connection_test_module,
    get_modified_remote_data_command,
    get_remote_data_command,
)
from ReliaquestTakedown import Client

UUID_REGEX = r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
TEST_URL = "https://test.com/api"


@pytest.fixture
def client() -> Client:
    return Client(
        base_url=TEST_URL, account_id="1234", access_key="HHHQW", secret_key="saddsadsajhhjksa", verify=False, proxy=False
    )


def create_comments_post(takedown_id):
    return {
        "content": "test comment",
        "created": "2025-03-12T06:21:36.986Z",
        "id": "c3894680-6d53-40c7-8129-fe896d48ce86",
        "takedown-id": takedown_id,
        "user": {"id": "71ddec65-2bc9-4051-92ed-5d9788d18e2f", "name": "API 'UAQJLB'"},
    }


def create_comments_get(takedown_id):
    return [
        {
            "content": "test comment",
            "created": "2025-03-12T06:21:36.986Z",
            "updated": "2025-03-12T06:21:36.986Z",
            "id": "c3894680-6d53-40c7-8129-fe896d48ce86",
            "takedown-id": takedown_id,
            "user": {"id": "71ddec65-2bc9-4051-92ed-5d9788d18e2f", "name": "API 'UAQJLB'"},
        }
    ]


def create_brand_list():
    return [
        {
            "id": "83d44b8c-fd65-48af-adbd-8163d53ab501",
            "name": "Digital Shadows - Sandbox Testing",
            "domain-name": "www.digitalshadows.com",
            "created": "2024-08-07T09:58:25.308Z",
            "updated": "2024-08-07T09:58:25.308Z",
            "status": "pending",
        },
        {
            "id": "ef2abd0c-6199-403d-b9bf-0b4cc2538dd1",
            "name": "JS Brand",
            "domain-name": "www.jaydeep.com",
            "created": "2025-02-21T10:11:12.953Z",
            "updated": "2025-02-21T10:11:12.953Z",
            "status": "pending",
        },
        {
            "id": "d5e868ec-bfc9-4744-8c0e-1d0c991c5085",
            "name": "Reliaquest",
            "domain-name": "www.rq.com",
            "created": "2024-08-09T12:40:51.888Z",
            "updated": "2024-08-09T12:40:51.888Z",
            "status": "pending",
        },
    ]


def create_takedown_get(limit=None):
    takedown_json = {
        "id": str(uuid.uuid4()),
        "created": "2025-03-11T14:38:07.450Z",
        "updated": "2025-03-11T14:38:07.530Z",
        "type": "malware",
        "status": "submitted",
        "brand": {
            "id": "d5e868ec-bfc9-4744-8c0e-1d0c991c5085",
            "name": "Reliaquest",
            "domain-name": "www.rq.com",
            "created": "2024-08-09T12:40:51.888Z",
            "updated": "2024-08-09T12:40:51.888Z",
            "status": "pending",
        },
        "targets": [{"id": "1b30e52a-f7f0-4cab-aeb5-ec3bfca270ae", "url": "hellotech.com"}],
    }
    if limit:
        return [takedown_json for _ in range(limit)]
    else:
        return takedown_json


def create_takedown_post(kwags):
    return {
        "id": str(uuid.uuid4()),
        "created": "2025-03-11T14:38:07.450Z",
        "updated": "2025-03-11T14:38:07.530Z",
        "type": kwags["type"],
        "status": "submitted",
        "brand": {
            "id": kwags["brand"],
            "name": "Reliaquest",
            "domain-name": "www.rq.com",
            "created": "2024-08-09T12:40:51.888Z",
            "updated": "2024-08-09T12:40:51.888Z",
            "status": "pending",
        },
        "targets": [{"id": "1b30e52a-f7f0-4cab-aeb5-ec3bfca270ae", "url": kwags["target"]["url"]}],
    }


class ClientMock:
    def http_request_side_effect(self, method, url_suffix, params=None, **kwargs):
        if url_suffix == "/v1/test":
            response = get_response()
        elif re.match(f"/v1/takedowns/attachments/{UUID_REGEX}/download", url_suffix) and method == "GET":
            return download_attachment_get()
        elif re.match(f"/v1/takedowns/{UUID_REGEX}/attachments", url_suffix) and method == "POST":
            response = get_attachment()
        elif re.match(f"/v1/takedowns/{UUID_REGEX}/attachments", url_suffix) and method == "GET":
            response = {}
        elif url_suffix == "/api/search/find":
            response = create_comments_post()
        elif re.match(f"/v1/takedowns/{UUID_REGEX}/comments", url_suffix) and method == "GET":
            takedownid = url_suffix.split("/")[3]
            response = create_comments_get(takedownid)
        elif re.match(f"/v1/takedowns/{UUID_REGEX}/comments", url_suffix) and method == "POST":
            takedownid = url_suffix.split("/")[3]
            response = create_comments_post(takedownid)

        elif re.match(r"^/v1/takedown-events\?limit=[^&]+&event-num-after=[^&]+$", url_suffix) and method == "GET":
            response = get_takedown_events()
        elif url_suffix == "/v1/takedown-brands":
            response = create_brand_list()
        elif url_suffix == "/v1/takedowns" and method == "GET":
            limit = params["limit"]
            response = create_takedown_get(limit)
        elif url_suffix == "/v1/takedowns" and method == "POST" and kwargs["json_data"]["brand"] == "rate_limit":
            return create_takedown_rate_limit()
        elif url_suffix == "/v1/takedowns" and method == "POST":
            response = create_takedown_post(kwargs["json_data"])
        elif re.match(f"/v1/takedowns/{UUID_REGEX}", url_suffix) and method == "GET":
            response = create_takedown_get()
        else:
            response = []

        return create_mocked_response(response)


def get_attachment():
    return {
        "id": "ea0146a9-47f4-41a4-b81e-7eca557c727a",
        "name": "constellation-brand.eml.msg",
        "length": 738816,
        "contentType": "application/octet-stream",
        "created": "2024-10-04T20:48:23.620Z",
    }


def create_takedown_rate_limit():
    response = requests.Response()
    response.headers = {"ratelimit-limit": "10", "ratelimit-remaining": "3", "ratelimit-reset": "1"}
    response.status_code = 403
    return response


def get_response():
    data = [
        {"message": "'accountId' is invalid"},
        {"api-key-valid": "accountId is invalid"},
        {"access-account-enabled": "accountId is invalid"},
        {"account-api-enabled": "accountId is invalid"},
        {"account-id-valid": "accountId is invalid"},
        {},
    ]
    random.shuffle(data)
    return data[1]


def get_takedown_events():
    get_incidents_list_response = load_test_data("test_data/get_events.json")
    return get_incidents_list_response


def download_attachment_get():
    path = "test_data/file-sample.pdf"
    try:
        with open(path, "rb") as file:
            file_data = file.read()
        response = requests.Response()
        response.status_code = 200
        response._content = file_data
        response.headers = {
            "Content-Type": "application/octet-stream",
            "Content-Disposition": 'attachment; filename="test/file-sample1.pdf"',
        }

        return response
    except FileNotFoundError:
        return requests.Response("File not found.", status_code=404)


def create_mocked_response(response, status_code: int = 200) -> requests.Response:
    mocked_response = requests.Response()
    mocked_response._content = json.dumps(response).encode("utf-8")
    mocked_response.status_code = status_code
    return mocked_response


def create_triage_item_events(num_of_events: int, start_event_num: int = 1):
    return [
        {
            "event-num": event_num,
            "event-created": "2020-09-24T16:30:10.016Z",
            "triage-item-id": event_num,
            "event-action": "create",
            "risk-level": "high",
            "risk-type": "test",
            "classification": "test",
            "state": "unread",
        }
        for event_num in range(start_event_num, num_of_events + start_event_num)
    ]


def test_fetch_takedown_command(mocker, client: Client):
    """
    Given:
     - 100 events

    When:
     - running the fetch_incidents_command

    Then:
     - make sure that all events are enriched and fetched (5000)
    """
    from ReliaquestTakedown import fetch_takedowns

    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)

    last_run = {"takedown": {"last_fetch": 0}}
    next_run, events = fetch_takedowns(10, last_run, client)
    assert len(events) == 10


def test_create_comment_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    takedown_id = "e796945e-943c-4b44-a5d2-4f897d04f81f"
    res = create_comment(client, {"takedown_id": takedown_id, "comment": "test comment"})
    assert len(res) is not None
    assert res["takedown-id"] == takedown_id


def test_list_brands_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    res = list_brands(client, {})
    assert len(res) is not None


def test_create_takedown_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    res = create_takedown(
        client,
        {
            "brand_id": "14718933-7fdf-484b-bd45-a873c8ac2fba",
            "type": "impersonation",
            "portal_id": "bhdfs",
            "target": "https://www.digitalshadowsresearch13.com/adobe",
        },
    )
    assert len(res) is not None


def test_download_attachment_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    try:
        download_attachment(client, {"attachmentId": "14718933-7fdf-484b-bd45-a873c8ac2fba"})
    except Exception:
        pass


def test_test_module_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    for _ in range(20):
        connection_test_module(client)


def load_test_data(json_path):
    with open(json_path) as f:
        return json.load(f)


def test_get_modified_remote_data(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    modified_ids, last_num = get_modified_remote_data_command(client, "0")
    assert len(modified_ids.modified_incident_ids) == 10
    assert modified_ids.modified_incident_ids == [
        "1f1fe26c-b310-415d-9c02-6212a692cbd7",
        "59df3261-a5fd-4353-8f7b-32f64d2c5016",
        "9c4a5e44-5c64-46b8-ae9f-72a5316ae07c",
        "18a9e57c-39c8-4799-b1e8-75f582bad994",
        "04fc661f-e940-4acf-bbc3-8ba44c39270d",
        "e0837d78-9b88-47c8-8f13-fc4474243867",
        "e796945e-943c-4b44-a5d2-4f897d04f81f",
        "ba269067-1b08-46ce-99af-c6fff088d0e7",
        "93a41e46-185b-4ed1-a9a2-1999234a6fb8",
        "1f1fe26c-b310-415d-9c02-6212a692cbd7",
    ]


def test_get_remote_data_command(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    response = get_remote_data_command(client, {"id": "1f1fe26c-b310-415d-9c02-6212a692cbd7", "lastUpdate": "2020-09-22"})
    assert response.mirrored_object is not None


def test_create_takedown_command_ratelimit(mocker, client: Client):
    http_mocker = ClientMock()
    mocker.patch.object(client, "_http_request", side_effect=http_mocker.http_request_side_effect)
    try:
        create_takedown(
            client,
            {"brand_id": "rate_limit", "type": "impersonation", "target": "https://www.digitalshadowsresearch13.com/adobe"},
        )
    except Exception:
        pass