GTI/VirusTotal Threat Intelligence Indicator Match


Description

Detects when an IP address, domain, or file hash in any log event matches a known malicious indicator from Google Threat Intelligence (GTI) / VirusTotal enrichment. Severity is elevated based on GTI's threat severity verdict and the number of vendors flagging the indicator as malicious.

Query · python

from panther_gti_helpers import (
    get_gti_object,
    gti_alert_context,
    gti_severity,
    severity_greater_than,
)

INDICATOR_FIELDS = (
    "p_any_ip_addresses",
    "p_any_domain_names",
    "p_any_md5_hashes",
    "p_any_sha1_hashes",
    "p_any_sha256_hashes",
)

MATCHED_INDICATORS = {}  # {indicator: indicator_type}


def _first(value):
    """Collapse a possibly list-shaped lookup value (multiple LUT hits) to a single value."""
    if isinstance(value, list):
        return next((entry for entry in value if entry), None)
    return value


def rule(event):
    global MATCHED_INDICATORS  # pylint: disable=global-statement
    MATCHED_INDICATORS = {}

    gti = get_gti_object(event)
    if not gti:
        return False

    for field in INDICATOR_FIELDS:
        for value in event.get(field, []) or []:
            if value in MATCHED_INDICATORS:
                continue
            indicator_type = _first(gti.indicator_type(value))
            if not indicator_type:
                continue
            if not gti.is_malicious(value):
                continue
            MATCHED_INDICATORS[value] = indicator_type

    return bool(MATCHED_INDICATORS)


def title(event):
    log_type = event.get("p_log_type", "Unknown")
    if len(MATCHED_INDICATORS) == 1:
        indicator, ioc_type = next(iter(MATCHED_INDICATORS.items()))
        return f"GTI: Known malicious {ioc_type} [{indicator}] detected in {log_type}"
    return f"GTI: {len(MATCHED_INDICATORS)} threat indicators detected in {log_type}"


def severity(event):
    highest = None
    for indicator in MATCHED_INDICATORS:
        sev = gti_severity(event, indicator)
        if highest is None or severity_greater_than(sev, highest):
            highest = sev
    return highest or "DEFAULT"


def alert_context(event):
    if not MATCHED_INDICATORS:
        return {}
    ctx = {}
    for indicator, indicator_type in MATCHED_INDICATORS.items():
        indicator_ctx = gti_alert_context(event, indicator)
        indicator_ctx["MatchedIndicatorType"] = indicator_type
        ctx[indicator] = indicator_ctx
    return ctx

Analyst notes

  1. Review the alert context and open the GTI/VirusTotal URL to assess the indicator's verdict, detection ratio, and suggested threat label.
  2. Query the data lake for all events involving this indicator to determine what assets or services were contacted and whether any connections were successful.
  3. If the indicator is confirmed malicious and interaction was observed, block the indicator, isolate affected hosts, and reset any credentials that may have been exposed.
Raw source GTI/VirusTotal Threat Intelligence Indicator Match · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: gti_malicious_indicator.py
RuleID: "Standard.GTI.MaliciousIndicator"
DisplayName: "GTI/VirusTotal Threat Intelligence Indicator Match"
Enabled: false
Status: Experimental
Severity: High
Description: >-
  Detects when an IP address, domain, or file hash in any log event matches a known
  malicious indicator from Google Threat Intelligence (GTI) / VirusTotal enrichment.
  Severity is elevated based on GTI's threat severity verdict and the number of
  vendors flagging the indicator as malicious.
Runbook: |
  1. Review the alert context and open the GTI/VirusTotal URL to assess the indicator's
     verdict, detection ratio, and suggested threat label.
  2. Query the data lake for all events involving this indicator to determine what assets
     or services were contacted and whether any connections were successful.
  3. If the indicator is confirmed malicious and interaction was observed, block the
     indicator, isolate affected hosts, and reset any credentials that may have been exposed.
Reference: https://www.virustotal.com
DedupPeriodMinutes: 60
Reports:
  MITRE ATT&CK:
    - TA0043:T1595.001
Tags:
  - Reconnaissance:Active Scanning
  - GTI
  - VirusTotal
  - Threat Intelligence
SummaryAttributes:
  - p_any_ip_addresses
  - p_source_label
LogTypes:
  - Amazon.EKS.Audit
  - Asana.Audit
  - Atlassian.Audit
  - AWS.ALB
  - AWS.CloudTrail
  - AWS.VPCFlow
  - Azure.Audit
  - Azure.MonitorActivity
  - Box.Event
  - Cloudflare.Firewall
  - Cloudflare.HttpRequest
  - Crowdstrike.FDREvent
  - GCP.AuditLog
  - GSuite.ActivityEvent
  - Notion.AuditLogs
  - Okta.SystemLog
  - OneLogin.Events
  - OnePassword.SignInAttempt
  - Zendesk.Audit
  - Zoom.Activity
Tests:
  - Name: "Known Malicious File Hash Detected - High Severity"
    ExpectedResult: true
    Log:
      p_any_sha256_hashes:
        - "0000000000000000000000000000000000000000000000000000000000000000"
      p_log_type: "Crowdstrike.FDREvent"
      p_enrichment:
        vt_iocstream:
          "0000000000000000000000000000000000000000000000000000000000000000":
            id: "0000000000000000000000000000000000000000000000000000000000000000"
            type: "file"
            type_description: "Win32 EXE"
            meaningful_name: "example-malware.exe"
            names:
              - "example-malware.exe"
              - "svchost32.exe"
            md5: "00000000000000000000000000000000"
            sha256: "0000000000000000000000000000000000000000000000000000000000000000"
            reputation: -12
            gti_url: "https://www.virustotal.com/gui/file/0000000000000000000000000000000000000000000000000000000000000000"
            last_analysis_stats:
              malicious: 57
              suspicious: 0
              harmless: 0
              undetected: 13
            threat_severity:
              threat_severity_level: "SEVERITY_HIGH"
              level_description: "Severity HIGH because it was considered trojan."
            popular_threat_classification:
              suggested_threat_label: "trojan.remcos/rescoms"
              popular_threat_category:
                - count: 32
                  value: "trojan"
              popular_threat_name:
                - count: 18
                  value: "remcos"
            tags:
              - "peexe"
              - "payload"
              - "malware"
  - Name: "Known Malicious IP Detected - Medium Detections"
    ExpectedResult: true
    Log:
      p_any_ip_addresses:
        - "203.0.113.5"
      p_log_type: "AWS.CloudTrail"
      p_enrichment:
        vt_iocstream:
          "203.0.113.5":
            id: "203.0.113.5"
            type: "ip_address"
            reputation: -5
            country: "NL"
            as_owner: "Example Hosting"
            asn: 64500
            last_analysis_stats:
              malicious: 5
              suspicious: 1
              harmless: 60
              undetected: 4
            tags:
              - "scanner"
  - Name: "Multiple Malicious Domains Detected"
    ExpectedResult: true
    Log:
      p_any_domain_names:
        - "evil-c2.example.com"
        - "phishing-page.example.com"
      p_log_type: "Cloudflare.HttpRequest"
      p_enrichment:
        vt_iocstream:
          "evil-c2.example.com":
            id: "evil-c2.example.com"
            type: "domain"
            reputation: -20
            categories:
              - "malware"
            last_analysis_stats:
              malicious: 30
              suspicious: 0
              harmless: 40
              undetected: 5
          "phishing-page.example.com":
            id: "phishing-page.example.com"
            type: "domain"
            reputation: -8
            categories:
              - "phishing"
            last_analysis_stats:
              malicious: 8
              suspicious: 2
              harmless: 55
              undetected: 5
  - Name: "Benign IP - No Enrichment"
    ExpectedResult: false
    Log:
      p_any_ip_addresses:
        - "10.0.0.1"
      p_log_type: "AWS.VPCFlow"
  - Name: "IP Not in GTI/VirusTotal Feed"
    ExpectedResult: false
    Log:
      p_any_ip_addresses:
        - "192.168.1.1"
      p_log_type: "Cloudflare.HttpRequest"
      p_enrichment:
        vt_iocstream:
          "192.168.1.1":
            id: ""
            type: ""
            last_analysis_stats:
              malicious: 0
              suspicious: 0
              harmless: 0
              undetected: 0
  - Name: "Known Benign Indicator - No Malicious Detections"
    ExpectedResult: false
    Log:
      p_any_ip_addresses:
        - "198.51.100.42"
      p_log_type: "AWS.VPCFlow"
      p_enrichment:
        vt_iocstream:
          "198.51.100.42":
            id: "198.51.100.42"
            type: "ip_address"
            reputation: 40
            country: "US"
            as_owner: "Example Cloud Provider"
            last_analysis_stats:
              malicious: 0
              suspicious: 0
              harmless: 80
              undetected: 4
  - Name: "Multiple LUT Matches For Same Value - List-Shaped Enrichment"
    ExpectedResult: true
    Log:
      p_any_sha256_hashes:
        - "1111111111111111111111111111111111111111111111111111111111111111"
      p_log_type: "Crowdstrike.FDREvent"
      p_enrichment:
        vt_iocstream:
          "1111111111111111111111111111111111111111111111111111111111111111":
            - id: "1111111111111111111111111111111111111111111111111111111111111111"
              type: "file"
              gti_url: "https://www.virustotal.com/gui/file/1111111111111111111111111111111111111111111111111111111111111111"
              last_analysis_stats:
                malicious: 42
                suspicious: 1
                harmless: 0
                undetected: 15
              threat_severity:
                threat_severity_level: "SEVERITY_HIGH"
                level_description: "Severity HIGH because it was considered trojan."


# ------ paired body: gti_malicious_indicator.py ------

from panther_gti_helpers import (
    get_gti_object,
    gti_alert_context,
    gti_severity,
    severity_greater_than,
)

INDICATOR_FIELDS = (
    "p_any_ip_addresses",
    "p_any_domain_names",
    "p_any_md5_hashes",
    "p_any_sha1_hashes",
    "p_any_sha256_hashes",
)

MATCHED_INDICATORS = {}  # {indicator: indicator_type}


def _first(value):
    """Collapse a possibly list-shaped lookup value (multiple LUT hits) to a single value."""
    if isinstance(value, list):
        return next((entry for entry in value if entry), None)
    return value


def rule(event):
    global MATCHED_INDICATORS  # pylint: disable=global-statement
    MATCHED_INDICATORS = {}

    gti = get_gti_object(event)
    if not gti:
        return False

    for field in INDICATOR_FIELDS:
        for value in event.get(field, []) or []:
            if value in MATCHED_INDICATORS:
                continue
            indicator_type = _first(gti.indicator_type(value))
            if not indicator_type:
                continue
            if not gti.is_malicious(value):
                continue
            MATCHED_INDICATORS[value] = indicator_type

    return bool(MATCHED_INDICATORS)


def title(event):
    log_type = event.get("p_log_type", "Unknown")
    if len(MATCHED_INDICATORS) == 1:
        indicator, ioc_type = next(iter(MATCHED_INDICATORS.items()))
        return f"GTI: Known malicious {ioc_type} [{indicator}] detected in {log_type}"
    return f"GTI: {len(MATCHED_INDICATORS)} threat indicators detected in {log_type}"


def severity(event):
    highest = None
    for indicator in MATCHED_INDICATORS:
        sev = gti_severity(event, indicator)
        if highest is None or severity_greater_than(sev, highest):
            highest = sev
    return highest or "DEFAULT"


def alert_context(event):
    if not MATCHED_INDICATORS:
        return {}
    ctx = {}
    for indicator, indicator_type in MATCHED_INDICATORS.items():
        indicator_ctx = gti_alert_context(event, indicator)
        indicator_ctx["MatchedIndicatorType"] = indicator_type
        ctx[indicator] = indicator_ctx
    return ctx

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.