Hoxhunt Deprecated

Deprecated. Use Hoxhunt V2 instead.

Email · Hoxhunt

Details

IDHoxhunt
ProviderHoxhunt
CategoryEmail
From Version6.10.0
Docker Imagedemisto/python3:3.12.12.5490952
Supported ModulesAgentix XSIAM

README

Use the Hoxhunt integration to send feedback to reporters of incidents, set incident sensitivity, and apply SOC classification to incidents.
This integration was integrated and tested with version August 2024 of Hoxhunt.

Configure Hoxhunt in Cortex

Parameter Description Required
Server URL (e.g. https://api.hoxhunt.com/graphql-external)   True
Trust any certificate (not secure)   False
Use system proxy settings   False
API Key Input your api key from Hoxhunt False
Fetch incidents   False
Incidents Fetch Interval   False
Incident type   False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)   False
Query Filter Input a query filter for incidents to be fetched 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.

hoxhunt-get-current-user


Gets the current user information from Hoxhunt.

Base Command

hoxhunt-get-current-user

Input

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

Context Output

Path Type Description
HoxHunt.CurrentUser string Current User information from Hoxhunt.

hoxhunt-get-incidents


Gets incidents from Hoxhunt.

Base Command

hoxhunt-get-incidents

Input

Argument Name Description Required
filter Input here the filter or other arguments. Optional

Context Output

Path Type Description
Hoxhunt.Incidents string Incidents from Hoxhunt.

hoxhunt-get-threats


Gets threats from Hoxhunt.

Base Command

hoxhunt-get-threats

Input

Argument Name Description Required
filter Input here the filter or other arguments. Optional

Context Output

Path Type Description
Hoxhunt.Threats string Threats from Hoxhunt.

hoxhunt-add-incident-note


Add Incident note.

Base Command

hoxhunt-add-incident-note

Input

Argument Name Description Required
incident_id The ID of the incident to which the note will be added. Required
note The content of the note to add to the incident. Required

Context Output

Path Type Description
Hoxhunt.addIncidentNode string Add Incident note.

hoxhunt-remove-incident-threats


Remove all threats that belong to an incident.

Base Command

hoxhunt-remove-incident-threats

Input

Argument Name Description Required
incident_id The ID of the incident from which threats will be removed. Required

Context Output

Path Type Description
Hoxhunt.removeIncidentThreats number Returns number of removed threats.

hoxhunt-send-incident-soc-feedback


Send feedback to reporters of incident about whether the reported email was safe, spam or malicious.

Base Command

hoxhunt-send-incident-soc-feedback

Input

Argument Name Description Required
incident_id The ID of the incident for which SOC feedback will be sent. Required
custom_message A custom message to include with the SOC feedback. Required
threat_feedback_reported_at_limit Datetime limit. Required

Context Output

Path Type Description
Hoxhunt.sendIncidentSocFeedback string The reporter will be informed the incident has been resolved and that no further actions are required from them.

hoxhunt-set-incident-sensitive


Set incident to contain sensitive information.

Base Command

hoxhunt-set-incident-sensitive

Input

Argument Name Description Required
incident_id The ID of the incident to be marked as sensitive or not sensitive. Required
is_sensitive True or False Boolean. Possible values are: TRUE, FALSE. Required

Context Output

Path Type Description
Hoxhunt.setIncidentSensitive string Incident sensitivity information.

hoxhunt-set-incident-soc-classification


Set soc classification for an incident.

Base Command

hoxhunt-set-incident-soc-classification

Input

Argument Name Description Required
incident_id The ID of the incident to classify. Required
classification The SOC classification to apply to the incident. Possible values are: MALICIOUS, SPAM, SAFE. Required

Context Output

Path Type Description
Hoxhunt.setIncidentSocClassification string Incident SOC classification information.

hoxhunt-update-incident-state


Updates Incident state.

Base Command

hoxhunt-update-incident-state

Input

Argument Name Description Required
incident_id The ID of the incident to update. Required
state The new state of the incident. Possible values are: OPEN, RESOLVED. Required

Context Output

Path Type Description
Hoxhunt.updateIncidentState string Incident state information.

Incident Mirroring

You can enable incident mirroring between Cortex XSOAR incidents and Hoxhunt 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 Hoxhunt.

Configuration parameters

  • url — Server URL (e.g. https://api.hoxhunt.com/graphql-external) (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • api_key — API Key
  • isFetch — Fetch incidents
  • incidentFetchInterval — Incidents Fetch Interval
  • incidentType — Incident type
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • queryfilter — Query Filter

Commands (9)

  • hoxhunt-add-incident-note

    Add Incident note.

  • hoxhunt-get-current-user

    Gets the current user information from Hoxhunt.

  • hoxhunt-get-incidents

    Gets incidents from Hoxhunt.

  • hoxhunt-get-threats

    Gets threats from Hoxhunt.

  • hoxhunt-remove-incident-threats

    Remove all threats that belong to an incident.

  • hoxhunt-send-incident-soc-feedback

    Send feedback to reporters of incident about whether the reported email was safe, spam or malicious.

  • hoxhunt-set-incident-sensitive

    Set incident to contain sensitive information.

  • hoxhunt-set-incident-soc-classification

    Set soc classification for an incident.

  • hoxhunt-update-incident-state

    Updates Incident state.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
''' CLIENT CLASS '''


class Client(BaseClient):
    def __init__(self, base_url: str, headers: dict, proxy: bool = False, verify: bool = False):
        super().__init__(base_url=base_url, headers=headers, proxy=proxy, verify=verify)

    def query(self, data, variables={}):
        return self._http_request(
            method='POST',
            json_data={'query': data, 'variables': variables})

    def add_incident_note(self, incident_id: Optional[str], note_text: Optional[str]) -> Dict[str, Any]:
        mutation = '''
        mutation CreateIncidentNote($incidentId: ID!, $note: IncidentNoteInput!) {
            addIncidentNote(
                incidentId: $incidentId
                note: $note
            ) {
                notes {
                    _id
                    text
                }
            }
        }
        '''
        variables = {
            'incidentId': incident_id,
            'note': {
                'text': note_text
            }
        }
        return self.query(mutation, variables)

    def remove_incident_threats(self, incident_id: Optional[str]) -> Dict[str, Any]:
        mutation = '''
        mutation RemoveIncidentThreats($incidentId: String!) {
          removeIncidentThreats(incidentId: $incidentId)
        }
        '''
        variables = {"incidentId": incident_id}
        return self.query(mutation, variables)

    def send_incident_soc_feedback(self, incident_id: Optional[str], custom_message: Optional[str] = None,
                                   threat_feedback_reported_at_limit: Optional[str] = None) -> Dict[str, Any]:
        mutation = '''
        mutation SendIncidentSocFeedback($incidentId: String!, $customMessage: String, $threatFeedbackReportedAtLimit: Date) {
            sendIncidentSocFeedback(
                incidentId: $incidentId,
                customMessage: $customMessage,
                threatFeedbackReportedAtLimit: $threatFeedbackReportedAtLimit
            )
        }
        '''
        variables = {
            'incidentId': incident_id,
            'customMessage': custom_message,
            'threatFeedbackReportedAtLimit': threat_feedback_reported_at_limit
        }
        return self.query(mutation, variables)

    def set_incident_sensitive(self, incident_id: Optional[str], is_sensitive: bool) -> Dict[str, Any]:
        mutation = '''
        mutation SetIncidentSensitive($incidentId: String!, $isSensitive: Boolean!) {
            setIncidentSensitive(
                incidentId: $incidentId
                isSensitive: $isSensitive
            ) {
                _id
                hasSensitiveInformation
            }
        }
        '''
        variables = {
            'incidentId': incident_id,
            'isSensitive': is_sensitive
        }
        response = self.query(mutation, variables)
        if 'errors' in response:
            raise ValueError(f"Failed to set incident sensitive: {response['errors']}")
        data = response.get('data', {}).get('setIncidentSensitive')
        if data is None:
            raise ValueError("Failed to set incident sensitive: No data returned.")
        return {
            '_id': data.get('_id'),
            'hasSensitiveInformation': data.get('hasSensitiveInformation'),
        }

    def set_incident_soc_classification(self, incident_id: Optional[str], classification: Optional[str]) -> Dict[str, Any]:
        mutation = '''
        mutation SetIncidentSocClassification($incidentId: String!, $classification: SocClassification!) {
            setIncidentSocClassification(
                incidentId: $incidentId
                classification: $classification
            ) {
                _id
                humanReadableId
                updatedAt
                socClassification
            }
        }
        '''
        variables = {
            'incidentId': incident_id,
            'classification': classification
        }
        return self.query(mutation, variables)

    def update_incident_state(self, incident_id: Optional[str], state: Optional[str]) -> Dict[str, Any]:
        mutation = '''
        mutation UpdateIncidentState($incidentId: ID!, $state: IncidentState!) {
            updateIncidentState(
                incidentId: $incidentId
                state: $state
            ) {
                _id
                humanReadableId
                updatedAt
                state
            }
        }
        '''
        variables = {
            'incidentId': incident_id,
            'state': state
        }
        return self.query(mutation, variables)


def test_module(client, query) -> str:
    result = client.query(query)
    if result:
        return 'ok'
    else:
        return 'Test failed: ' + str(result)


def create_output(results: Dict[str, str], endpoint: str, keyfield: str = '') -> CommandResults:
    human_readable = tableToMarkdown('Hoxhunt results', results)
    return CommandResults(
        outputs_prefix=f'Hoxhunt.{endpoint}',
        outputs_key_field=keyfield,
        outputs=results,
        readable_output=human_readable
    )


def create_incident_from_log(incident: Dict[str, Any]) -> Dict[str, Any]:
    occurred = incident.get('createdAt')
    humanreadable = incident.get('humanReadableId')
    dbotMirrorId = str(incident.get('_id'))
    keys = incident.keys()
    labels = []
    for key in keys:
        labels.append({'type': key, 'value': str(incident[key])})
        formatted_description = f'Hoxhunt Incident: {humanreadable}'
    return {
        'name': formatted_description,
        'dbotMirrorId': dbotMirrorId,
        'rawJSON': json.dumps(incident),
        'occurred': occurred
    }


def form_incindents(incidents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    listofincidents = []
    for item in incidents:
        listofincidents.append(create_incident_from_log(item))
    return listofincidents


def fetch_incidents(client: Client, fetch_time: str, queryfilter: str):
    fetch_time_parsed = dateparser.parse(fetch_time)
    if fetch_time_parsed is None:
        raise ValueError("Invalid fetch_time format")
    timefrom = fetch_time_parsed.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
    last_run = demisto.getLastRun()
    if last_run and 'start_time' in last_run:
        start_time = last_run.get('start_time')
    else:
        start_time = timefrom
    fields = '''_id, createdAt, updatedAt, humanReadableId, lastReportedAt, firstReportedAt,
                policyName, state, threats{_id, createdAt, updatedAt, severity, feedbackSentAt,
                ratedAt, state, userRequestedFeedback, reporterUser{_id, emails{address}}, organizationId,
                organization{_id, name}, email{to{address}, from{address}, subject},
                enrichments{links{href, score}}, userModifiers{userActedOnThreat, repliedToEmail, downloadedFile,
                openedAttachment, visitedLink, enteredCredentials, userMarkedAsSpam, forwardedEmail, other},
                threatRedirectId, prediction{mlopsIocEmailMaliciousnessProbability,
                mlopsIocEmailMaliciousFlags{flag}}, classification, isVipReport}, organization{_id, name},
                organizationId, threatCount, globalThreatCount, notes{_id, user{emails{address}}, text,
                timestamp, editedAt, deletedAt}, severity, escalation{escalatedAt, creationThreshold},
                classification, socClassification, hasSensitiveInformation,
                ruleMatches{incidentRule{_id, name, priority}}'''
    if queryfilter:
        query = f'''
        {{
          incidents(sort: createdAt_DESC, filter: {{ {queryfilter}, createdAt_gt: "{start_time}" }}) {{
            {fields}
          }}
        }}
        '''
    else:
        query = f'''
        {{
          incidents(sort: createdAt_DESC, filter: {{createdAt_gt: "{start_time}" }}) {{
            {fields}
          }}
        }}
        '''
    results = client.query(query)
    incidents = results.get('data', {}).get('incidents')
    if incidents and len(incidents) > 0:
        end_time = results.get('data', {}).get('incidents', [{}])[0].get('createdAt')
        demisto.setLastRun({'start_time': end_time})
        demisto.incidents(form_incindents(results.get('data', {}).get('incidents')))
    else:
        demisto.incidents([])


def main():
    base_url = demisto.params().get('url')

    verify_certificate = not demisto.params().get('insecure', False)

    proxy = demisto.params().get('proxy', False)
    api_key = demisto.params().get('api_key')

    headers = {'Authorization': f'Authtoken {api_key}'}

    demisto.info(f'Command being called is {demisto.command()}')

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

        if demisto.command() == 'test-module':
            # This is the call made when pressing the integration Test button.
            query = '{currentUser {emails {address}}}'
            result = test_module(client, query)
            return_results(result)
        elif demisto.command() == 'fetch-incidents':
            fetch_time = demisto.params().get('fetch_time')
            queryfilter = demisto.params().get('queryfilter')
            fetch_incidents(client, fetch_time, queryfilter)
        elif demisto.command() == 'hoxhunt-get-current-user':
            # Get the current user's emails, no arguments required
            query = '{currentUser {emails {address}}}'
            results = client.query(query)
            return_results(create_output(results.get('data', {}).get('currentUser', {}).get('emails'), 'CurrentUser'))
        elif demisto.command() == 'hoxhunt-get-incidents':
            # Retrieve incidents from the Hoxhunt platform
            # Arguments:
            #   filter (str): Optional filter string to narrow down the incidents retrieved.
            queryfilter = args.get('filter')
            fields = '''_id, state, classification, socClassification,
                        hasSensitiveInformation, threatCount, createdAt,
                        updatedAt, humanReadableId, policyName, organizationId'''
            query = f'{{incidents({queryfilter}){{{fields}}}}}' if queryfilter else f'{{incidents{{{fields}}}}}'
            results = client.query(query)
            return_results(create_output(results.get('data', {}).get('incidents', []), 'Incidents', '_id'))
        elif demisto.command() == 'hoxhunt-get-threats':
            # Retrieve threats from the Hoxhunt platform
            # Arguments:
            #   filter (str): Optional filter string to narrow down the threats retrieved.
            queryfilter = args.get('filter')
            fields = '''_id, createdAt, updatedAt, feedbackSentAt, classification,
                        state, organizationId, severity, userRequestedFeedback,
                        threatRedirectId, isVipReport'''
            query = f'{{threats({queryfilter}){{{fields}}}}}' if queryfilter else f'{{threats{{{fields}}}}}'
            results = client.query(query)
            threats = results.get('data', {}).get('threats', [])
            return_results(create_output(threats, 'Threats', '_id'))
        elif demisto.command() == 'hoxhunt-add-incident-note':
            # Add a note to an incident
            # Arguments:
            #   incident_id (str): The ID of the incident to which the note will be added.
            #   note (str): The content of the note to add to the incident.
            incident_id = args.get('incident_id')
            note_text = args.get('note')
            response = client.add_incident_note(incident_id, note_text)
            if not response.get('errors'):
                note_data = response.get('data', {}).get('addIncidentNote', {}).get('notes', [])
                note = note_data[-1]
                transformed_data = {'incident_id': incident_id, 'note_id': note.get('_id', ''),
                                    'note': note.get('text', '')}
                return_results(create_output(transformed_data, 'IncidentNote', 'note_id'))
            else:
                return_error(response.get('errors'))
        elif demisto.command() == 'hoxhunt-remove-incident-threats':
            # Remove all threats that belong to an incident.
            # Arguments:
            #   incident_id (str): The ID of the incident from which threats will be removed.
            incident_id = args.get('incident_id')
            response = client.remove_incident_threats(incident_id)
            if response.get('data'):
                result_message = response.get('data', {}).get('removeIncidentThreats', 0)
                result_data = {'incident_id': incident_id, 'removed threats number': result_message}
                return_results(create_output(result_data, 'RemoveIncidentThreats'))
            else:
                return_error(response.get('errors'))
        elif demisto.command() == 'hoxhunt-send-incident-soc-feedback':
            # Send feedback to reporters of incident about whether the reported email was safe, spam or malicious.
            # Arguments:
            #   incident_id (str): The ID of the incident for which SOC feedback will be sent.
            #   custom_message (str): A custom message to include with the SOC feedback.
            #   threat_feedback_reported_at_limit (str): Datetime limit.
            incident_id = args.get('incident_id')
            custom_message = args.get('custom_message')
            threat_feedback_reported_at_limit = args.get('threat_feedback_reported_at_limit')
            response = client.send_incident_soc_feedback(incident_id, custom_message, threat_feedback_reported_at_limit)
            if response.get('data'):
                result_data = {'_id': incident_id, 'custom_message': custom_message,
                               'limit date': threat_feedback_reported_at_limit}
                return_results(create_output(result_data, 'SendIncidentSocFeedback'))
            else:
                return_error(response.get('errors'))
        elif demisto.command() == 'hoxhunt-set-incident-sensitive':
            # Mark an incident as sensitive or not sensitive
            # Arguments:
            #   incident_id (str): The ID of the incident to be marked as sensitive or not sensitive.
            #   is_sensitive (str): Set 'true' to mark the incident as sensitive, 'false' to mark it as not sensitive.
            incident_id = args.get('incident_id')
            is_sensitive_str = args.get('is_sensitive', 'false').lower()
            is_sensitive_bool = argToBoolean(is_sensitive_str)
            response = client.set_incident_sensitive(incident_id, is_sensitive_bool)
            sensitive_data: Dict[str, str] = {'incident_id': str(response.get('_id', '')),
                                              'is_sensitive': str(response.get('hasSensitiveInformation', ''))}
            return_results(create_output(sensitive_data, 'SetIncidentSensitive', 'incident_id'))
        elif demisto.command() == 'hoxhunt-set-incident-soc-classification':
            # Set SOC classification for an incident
            # Arguments:
            #   incident_id (str): The ID of the incident to classify.
            #   classification (str): The SOC classification to apply to the incident (e.g., MALICIOUS,SPAM,SAFE).
            incident_id = args.get('incident_id')
            classification = args.get('classification')
            response = client.set_incident_soc_classification(incident_id, classification)
            if not response.get('errors'):
                soc_classification_data = response.get('data', {}).get('setIncidentSocClassification', {})
                return_results(create_output(soc_classification_data, 'SetIncidentSocClassification', '_id'))
            else:
                return_error(response.get('errors'))
        elif demisto.command() == 'hoxhunt-update-incident-state':
            # Update the state of an incident
            # Arguments:
            #   incident_id (str): The ID of the incident to update.
            #   state (str): The new state of the incident (e.g., RESOLVED, OPEN).
            incident_id = args.get('incident_id')
            state = args.get('state')
            response = client.update_incident_state(incident_id, state)
            if not response.get('errors'):
                incident_state_data = response.get('data', {}).get('updateIncidentState', {})
                return_results(create_output(incident_state_data, 'UpdateIncidentState', '_id'))
            else:
                return_error(response.get('errors'))

    # Log exceptions
    except Exception as e:
        return_error(f'Failed to execute {demisto.command()} command. Error: {str(e)}')


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