Detection rules › Panther

Panther rules: otx

OTX Threat Intelligence Indicator Match

#
Severity
high
Entities
domain_names, emails, ip_addresses, sha256_hashes
Log types
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
Tags
Reconnaissance:Active Scanning, OTX, Threat Intelligence
Reference
otx.alienvault.com
Source
github.com/panther-labs/panther-analysis

Detects when an IP address in any log event matches a known threat indicator from AlienVault OTX pulse intelligence. Severity is elevated when the pulse includes a named adversary or known malware families.

MITRE ATT&CK coverage

TacticTechniques
Reconnaissance

Telemetry coverage

Detection logic

from panther_otx_helpers import (
    get_otx_object,
    otx_alert_context,
    otx_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",
    "p_any_emails",
)

MATCHED_INDICATORS = {}  # {indicator: indicator_type}


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

    otx = get_otx_object(event)
    if not otx:
        return False

    for field in INDICATOR_FIELDS:
        for value in event.get(field, []) or []:
            if value in MATCHED_INDICATORS:
                continue
            indicator_type = otx.indicator_type(value)
            if not indicator_type:
                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"OTX: Known threat {ioc_type} [{indicator}] detected in {log_type}"
    return f"OTX: {len(MATCHED_INDICATORS)} threat indicators detected in {log_type}"


def severity(event):
    highest = None
    for indicator in MATCHED_INDICATORS:
        sev = otx_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 = otx_alert_context(event, indicator)
        indicator_ctx["MatchedIndicatorType"] = indicator_type
        ctx[indicator] = indicator_ctx
    return ctx

Rule specification

AnalysisType: rule
Filename: otx_malicious_indicator.py
RuleID: "Standard.OTX.MaliciousIndicator"
DisplayName: "OTX Threat Intelligence Indicator Match"
Enabled: true
Severity: High
Description: >-
  Detects when an IP address in any log event matches a known threat indicator
  from AlienVault OTX pulse intelligence. Severity is elevated when the pulse
  includes a named adversary or known malware families.
Runbook: |
  1. Review the alert context and open the OTX Pulse URL to assess the indicator's
     threat context, associated adversary, malware families, and MITRE ATT&CK mappings.
  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://otx.alienvault.com
DedupPeriodMinutes: 60
Reports:
  MITRE ATT&CK:
    - TA0043:T1595.001
Tags:
  - Reconnaissance:Active Scanning
  - OTX
  - 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

Stages and Predicates

Rule logic imperative Python

The parser could not express this rule's Python logic as a structured condition; the complete logic is under Detection logic above.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
p_log_type

Response runbook

1. Review the alert context and open the OTX Pulse URL to assess the indicator's

threat context, associated adversary, malware families, and MITRE ATT&CK mappings.

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.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "p_any_ip_addresses": [
    "198.51.100.23"
  ],
  "p_enrichment": {
    "pulses_otx": {
      "198.51.100.23": {
        "adversary": "APT28",
        "attack_ids": [
          "T1071",
          "T1059"
        ],
        "created": "2024-01-15T10:30:00",
        "description": "Known command and control infrastructure used by APT28.",
        "id": "6141bd9b4e9aaa4bdd26b2a8",
        "indicator": "198.51.100.23",
        "indicator_created": "2024-01-15T10:30:00",
        "indicator_expiration": "2025-01-15T10:30:00",
        "indicator_type": "IPv4",
        "industries": [
          "government",
          "defense"
        ],
        "malware_families": [
          "X-Agent",
          "Sofacy"
        ],
        "modified": "2024-06-01T08:00:00",
        "name": "APT28 Infrastructure",
        "references": [
          "https://example.com/apt28-report"
        ],
        "tags": [
          "apt28",
          "c2",
          "russia"
        ],
        "target_countries": [
          "US",
          "DE"
        ],
        "tlp": "white"
      }
    }
  },
  "p_log_type": "AWS.CloudTrail"
}

Query.OTX.HighImpactPulses

#

This is an enrichment or summary query that produces aggregate or lookup data for other rules to consume, not a standalone detection. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Tags
OTX, Threat Intelligence, Lookup Table
Source
github.com/panther-labs/panther-analysis

Identify high-impact OTX pulses by grouping per-indicator rows and surfacing pulses with a named adversary or known malware families. Each pulse is scored CRITICAL (adversary AND malware), HIGH (one of the two), or filtered out. Results are ordered by severity and indicator volume to highlight the most noteworthy threats reported recently.

Rule specification

AnalysisType: saved_query
QueryName: "Query.OTX.HighImpactPulses"
Description: >
  Identify high-impact OTX pulses by grouping per-indicator rows and surfacing
  pulses with a named adversary or known malware families. Each pulse is scored
  CRITICAL (adversary AND malware), HIGH (one of the two), or filtered out.
  Results are ordered by severity and indicator volume to highlight the most
  noteworthy threats reported recently.
Tags:
  - OTX
  - Threat Intelligence
  - Lookup Table
SnowflakeQuery: |
  WITH pulse_summary AS (
      SELECT
          id AS pulse_id,
          ANY_VALUE(name) AS pulse_name,
          ANY_VALUE(description) AS description,
          ANY_VALUE(adversary) AS adversary,
          ANY_VALUE(malware_families) AS malware_families,
          ANY_VALUE(industries) AS industries,
          ANY_VALUE(target_countries) AS target_countries,
          ANY_VALUE(attack_ids) AS attack_ids,
          ANY_VALUE(tags) AS tags,
          ANY_VALUE(tlp) AS tlp,
          ANY_VALUE(modified) AS pulse_modified,
          COUNT(*) AS indicator_count,
          ARRAY_SIZE(ARRAY_AGG(DISTINCT indicator_type)) AS distinct_indicator_types,
          ARRAY_AGG(DISTINCT indicator_type) WITHIN GROUP (ORDER BY indicator_type) AS indicator_types
      FROM panther_lookups.public.otx_pulses
      WHERE p_occurs_since('30 days')
      GROUP BY id
  )
  SELECT
      pulse_id,
      pulse_name,
      description,
      adversary,
      malware_families,
      industries,
      target_countries,
      attack_ids,
      tags,
      tlp,
      indicator_count,
      distinct_indicator_types,
      indicator_types,
      pulse_modified,
      CASE
          WHEN adversary IS NOT NULL AND adversary <> '' AND ARRAY_SIZE(malware_families) > 0
              THEN 'CRITICAL'
          ELSE 'HIGH'
      END AS pulse_severity,
      CONCAT('https://otx.alienvault.com/pulse/', pulse_id) AS otx_url
  FROM pulse_summary
  WHERE
      (adversary IS NOT NULL AND adversary <> '')
      OR ARRAY_SIZE(malware_families) > 0
  ORDER BY pulse_severity, indicator_count DESC, pulse_modified DESC
  LIMIT 100

DatabricksQuery: |
  WITH pulse_summary AS (
      SELECT
          id AS pulse_id,
          ANY_VALUE(name) AS pulse_name,
          ANY_VALUE(description) AS description,
          ANY_VALUE(adversary) AS adversary,
          ANY_VALUE(malware_families) AS malware_families,
          ANY_VALUE(industries) AS industries,
          ANY_VALUE(target_countries) AS target_countries,
          ANY_VALUE(attack_ids) AS attack_ids,
          ANY_VALUE(tags) AS tags,
          ANY_VALUE(tlp) AS tlp,
          ANY_VALUE(modified) AS pulse_modified,
          COUNT(*) AS indicator_count,
          SIZE(COLLECT_SET(indicator_type)) AS distinct_indicator_types,
          ARRAY_SORT(COLLECT_SET(indicator_type)) AS indicator_types
      FROM panther_lookups.otx_pulses
      WHERE p_occurs_since('30 days')
      GROUP BY id
  )
  SELECT
      pulse_id,
      pulse_name,
      description,
      adversary,
      malware_families,
      industries,
      target_countries,
      attack_ids,
      tags,
      tlp,
      indicator_count,
      distinct_indicator_types,
      indicator_types,
      pulse_modified,
      CASE
          WHEN adversary IS NOT NULL AND adversary <> '' AND SIZE(malware_families) > 0
              THEN 'CRITICAL'
          ELSE 'HIGH'
      END AS pulse_severity,
      CONCAT('https://otx.alienvault.com/pulse/', pulse_id) AS otx_url
  FROM pulse_summary
  WHERE
      (adversary IS NOT NULL AND adversary <> '')
      OR SIZE(malware_families) > 0
  ORDER BY pulse_severity, indicator_count DESC, pulse_modified DESC
  LIMIT 100

Stages and Predicates

Stage 1: source

Table
pulse_summary

Stage 2: filter

  • adversary is present
  • adversary is not ""

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
adversaryis_not_null
  • (no value, null check)
field:"adversary" kind:is_not_null

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
pulse_id
pulse_name
description
adversary
malware_families
industries
target_countries
attack_ids
tags
tlp
indicator_count
distinct_indicator_types
indicator_types
pulse_modified
pulse_severityCASE WHEN adversary IS NOT NULL AND adversary <> '' AND ARRAY_SIZE ( malware_families ) > 0 THEN 'CRITICAL' ELSE 'HIGH' END
otx_urlCONCAT ( 'https://otx.alienvault.com/pulse/' , pulse_id )

Query.OTX.PulsesSummary

#

This is an enrichment or summary query that produces aggregate or lookup data for other rules to consume, not a standalone detection. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Tags
OTX, Threat Intelligence, Lookup Table
Source
github.com/panther-labs/panther-analysis

Group OTX pulse rows by pulse and summarize the indicators each pulse contains. The OTX.Pulses lookup table stores one indicator per row; this query reconstructs the pulse view with counts by indicator type, adversary, malware families, industries, and target countries.

Rule specification

AnalysisType: saved_query
QueryName: "Query.OTX.PulsesSummary"
Description: >
  Group OTX pulse rows by pulse and summarize the indicators each pulse contains.
  The OTX.Pulses lookup table stores one indicator per row; this query reconstructs
  the pulse view with counts by indicator type, adversary, malware families,
  industries, and target countries.
Tags:
  - OTX
  - Threat Intelligence
  - Lookup Table
SnowflakeQuery: |
  SELECT
      id AS pulse_id,
      ANY_VALUE(name) AS pulse_name,
      ANY_VALUE(adversary) AS adversary,
      ANY_VALUE(malware_families) AS malware_families,
      ANY_VALUE(tags) AS tags,
      ANY_VALUE(industries) AS industries,
      ANY_VALUE(target_countries) AS target_countries,
      ANY_VALUE(attack_ids) AS attack_ids,
      ANY_VALUE(tlp) AS tlp,
      ANY_VALUE(created) AS pulse_created,
      ANY_VALUE(modified) AS pulse_modified,
      COUNT(*) AS indicator_count,
      ARRAY_AGG(DISTINCT indicator_type) WITHIN GROUP (ORDER BY indicator_type) AS indicator_types,
      COUNT_IF(indicator_type ILIKE 'IPv4' OR indicator_type ILIKE 'IPv6') AS ip_count,
      COUNT_IF(indicator_type ILIKE 'domain' OR indicator_type ILIKE 'hostname') AS domain_count,
      COUNT_IF(indicator_type ILIKE 'FileHash-%') AS hash_count,
      COUNT_IF(indicator_type ILIKE 'URL' OR indicator_type ILIKE 'URI') AS url_count,
      COUNT_IF(indicator_type ILIKE 'email') AS email_count,
      COUNT_IF(indicator_type ILIKE 'CVE') AS cve_count,
      CONCAT('https://otx.alienvault.com/pulse/', id) AS otx_url
  FROM panther_lookups.public.otx_pulses
  WHERE p_occurs_since('30 days')
  GROUP BY id
  ORDER BY pulse_modified DESC
  LIMIT 200

DatabricksQuery: |
  SELECT
      id AS pulse_id,
      ANY_VALUE(name) AS pulse_name,
      ANY_VALUE(adversary) AS adversary,
      ANY_VALUE(malware_families) AS malware_families,
      ANY_VALUE(tags) AS tags,
      ANY_VALUE(industries) AS industries,
      ANY_VALUE(target_countries) AS target_countries,
      ANY_VALUE(attack_ids) AS attack_ids,
      ANY_VALUE(tlp) AS tlp,
      ANY_VALUE(created) AS pulse_created,
      ANY_VALUE(modified) AS pulse_modified,
      COUNT(*) AS indicator_count,
      ARRAY_SORT(COLLECT_SET(indicator_type)) AS indicator_types,
      COUNT_IF(indicator_type ILIKE 'IPv4' OR indicator_type ILIKE 'IPv6') AS ip_count,
      COUNT_IF(indicator_type ILIKE 'domain' OR indicator_type ILIKE 'hostname') AS domain_count,
      COUNT_IF(indicator_type ILIKE 'FileHash-%') AS hash_count,
      COUNT_IF(indicator_type ILIKE 'URL' OR indicator_type ILIKE 'URI') AS url_count,
      COUNT_IF(indicator_type ILIKE 'email') AS email_count,
      COUNT_IF(indicator_type ILIKE 'CVE') AS cve_count,
      CONCAT('https://otx.alienvault.com/pulse/', id) AS otx_url
  FROM panther_lookups.otx_pulses
  WHERE p_occurs_since('30 days')
  GROUP BY id
  ORDER BY pulse_modified DESC
  LIMIT 200

Stages and Predicates

Stage 1: source

Table
panther_lookups.public.otx_pulses

Stage 2: filter

Grouped by
id
Window
30d

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
pulse_idid
pulse_nameANY_VALUE ( name )
adversaryANY_VALUE ( adversary )
malware_familiesANY_VALUE ( malware_families )
tagsANY_VALUE ( tags )
industriesANY_VALUE ( industries )
target_countriesANY_VALUE ( target_countries )
attack_idsANY_VALUE ( attack_ids )
tlpANY_VALUE ( tlp )
pulse_createdANY_VALUE ( created )
pulse_modifiedANY_VALUE ( modified )
indicator_countCOUNT ( * )
indicator_typesARRAY_AGG ( DISTINCT indicator_type ) WITHIN GROUP ( ORDER BY indicator_type )
ip_countCOUNT_IF ( indicator_type ILIKE 'IPv4' OR indicator_type ILIKE 'IPv6' )
domain_countCOUNT_IF ( indicator_type ILIKE 'domain' OR indicator_type ILIKE 'hostname' )
hash_countCOUNT_IF ( indicator_type ILIKE 'FileHash-%' )
url_countCOUNT_IF ( indicator_type ILIKE 'URL' OR indicator_type ILIKE 'URI' )
email_countCOUNT_IF ( indicator_type ILIKE 'email' )
cve_countCOUNT_IF ( indicator_type ILIKE 'CVE' )
otx_urlCONCAT ( 'https://otx.alienvault.com/pulse/' , id )