Skip to content

Conversation

@tomsonpl
Copy link
Contributor

@tomsonpl tomsonpl commented Dec 3, 2025

WMI Persistence Event Subscriptions

Summary

This PR adds an osquery saved query to detect WMI Event Subscription persistence (MITRE ATT&CK T1546.003) on Windows systems. WMI eventing is a powerful persistence mechanism that allows attackers to execute arbitrary commands or scripts when specific system events occur, making it difficult to detect without specialized tooling.

The query provides complete forensic visibility by detecting:

  • Bound subscriptions - Active persistence (filter + consumer + binding)
  • Orphaned components - Residual artifacts from partially removed malware

Results are enriched with file hashes and code signatures for referenced executables and scripts.

Core Forensic Artifacts Coverage

# Artifact OS Query File Description
1 WMI Event Subscriptions Windows wmi_persistence_event_subscriptions_windows_elastic 40033716 Detects WMI persistence including bound subscriptions and orphaned components

MITRE ATT&CK Coverage

Technique ID Technique Name Tactic
T1546.003 Event Triggered Execution: Windows Management Instrumentation Event Subscription Persistence (TA0003)

Queries by Platform


🪟 Windows - WMI Event Subscription Persistence Detection

Description

Detects WMI event subscriptions used for persistence (MITRE ATT&CK T1546.003). Includes bound subscriptions (active persistence) AND orphaned components (filters/consumers without bindings) which may indicate residual or partially removed malware. WMI eventing allows attackers to execute arbitrary commands or scripts when specific system events occur. Enriched with file hashes and code signatures for referenced executables and scripts.

Detection Focus:

  • Bound subscriptions - Complete filter-consumer chains indicating active persistence
  • CommandLineEventConsumer - Executes arbitrary commands/executables on trigger
  • ActiveScriptEventConsumer - Executes VBScript/JScript on trigger
  • Orphaned consumers - Consumers without bindings (possible residual malware)
  • Orphaned filters - Filters without bindings (possible residual malware)
  • File hash enrichment - MD5/SHA256 for referenced executables
  • Code signature verification - Authenticode validation for executables

Result

Screenshot 2025-12-08 at 16 08 29

Query returns WMI subscription components with their relationships and enrichment data:

Status Description
bound Active persistence - filter and consumer are linked
orphaned_consumer Consumer exists without binding (residual malware artifact)
orphaned_filter Filter exists without binding (residual malware artifact)

Platform

windows

Interval

3600 seconds (1 hour)

Query ID

wmi_persistence_event_subscriptions_windows_elastic

ECS Field Mappings

Event Context:

  • event.category["configuration"]
  • event.type["info"]
  • event.kind"state"
  • event.module"osquery"
  • event.dataset"osquery.wmi_persistence"
  • host.os.type"windows"

Process/Executable Fields:

  • process.command_linecommand_line_template
  • process.executableexecutable_path
  • process.hash.md5md5
  • process.hash.sha256sha256
  • process.code_signature.subject_namesignature_signer
  • process.code_signature.statussignature_status

File Fields:

  • file.pathscript_file_name

Threat Context:

  • threat.framework"MITRE ATT&CK"
  • threat.tactic.id["TA0003"]
  • threat.tactic.name["Persistence"]
  • threat.technique.id["T1546.003"]
  • threat.technique.name["Event Triggered Execution: Windows Management Instrumentation Event Subscription"]

Tags:

  • osquery, persistence, wmi, event_subscription, mitre_t1546_003, windows

SQL Query

-- WMI Persistence Event Subscriptions - Complete Coverage (T1546.003)
-- Detects bound subscriptions and orphaned components for full forensic visibility
-- Fixed: Uses relative_path for proper JOIN matching per osquery schema

WITH wmi_subscriptions AS (
    -- Part 1: Bound subscriptions (complete filter-consumer chains)
    SELECT
        'bound' AS status,
        filter.name AS filter_name,
        filter.query AS filter_query,
        filter.query_language,
        filter.class AS filter_class,
        COALESCE(cli.name, script.name) AS consumer_name,
        CASE
            WHEN cli.name IS NOT NULL THEN 'CommandLineEventConsumer'
            WHEN script.name IS NOT NULL THEN 'ActiveScriptEventConsumer'
            ELSE 'Unknown'
        END AS consumer_type,
        COALESCE(cli.class, script.class) AS consumer_class,
        COALESCE(cli.relative_path, script.relative_path) AS consumer_relative_path,
        cli.command_line_template,
        cli.executable_path,
        script.scripting_engine,
        script.script_file_name,
        script.script_text,
        COALESCE(
            NULLIF(cli.executable_path, ''),
            NULLIF(cli.command_line_template, ''),
            NULLIF(script.script_file_name, '')
        ) AS hashable_path
    FROM wmi_filter_consumer_binding binding
    LEFT JOIN wmi_event_filters filter ON binding.filter = filter.relative_path
    LEFT JOIN wmi_cli_event_consumers cli ON binding.consumer = cli.relative_path
    LEFT JOIN wmi_script_event_consumers script ON binding.consumer = script.relative_path

    UNION ALL

    -- Part 2: Orphaned CLI consumers (possible residual malware)
    SELECT
        'orphaned_consumer' AS status,
        '' AS filter_name,
        '' AS filter_query,
        '' AS query_language,
        '' AS filter_class,
        cli.name AS consumer_name,
        'CommandLineEventConsumer' AS consumer_type,
        cli.class AS consumer_class,
        cli.relative_path AS consumer_relative_path,
        cli.command_line_template,
        cli.executable_path,
        '' AS scripting_engine,
        '' AS script_file_name,
        '' AS script_text,
        COALESCE(NULLIF(cli.executable_path, ''), NULLIF(cli.command_line_template, '')) AS hashable_path
    FROM wmi_cli_event_consumers cli
    WHERE NOT EXISTS (
        SELECT 1 FROM wmi_filter_consumer_binding binding
        WHERE binding.consumer = cli.relative_path
    )

    UNION ALL

    -- Part 3: Orphaned Script consumers (possible residual malware)
    SELECT
        'orphaned_consumer' AS status,
        '' AS filter_name,
        '' AS filter_query,
        '' AS query_language,
        '' AS filter_class,
        script.name AS consumer_name,
        'ActiveScriptEventConsumer' AS consumer_type,
        script.class AS consumer_class,
        script.relative_path AS consumer_relative_path,
        '' AS command_line_template,
        '' AS executable_path,
        script.scripting_engine,
        script.script_file_name,
        script.script_text,
        NULLIF(script.script_file_name, '') AS hashable_path
    FROM wmi_script_event_consumers script
    WHERE NOT EXISTS (
        SELECT 1 FROM wmi_filter_consumer_binding binding
        WHERE binding.consumer = script.relative_path
    )

    UNION ALL

    -- Part 4: Orphaned filters (possible residual malware)
    SELECT
        'orphaned_filter' AS status,
        filter.name AS filter_name,
        filter.query AS filter_query,
        filter.query_language,
        filter.class AS filter_class,
        '' AS consumer_name,
        '' AS consumer_type,
        '' AS consumer_class,
        '' AS consumer_relative_path,
        '' AS command_line_template,
        '' AS executable_path,
        '' AS scripting_engine,
        '' AS script_file_name,
        '' AS script_text,
        '' AS hashable_path
    FROM wmi_event_filters filter
    WHERE NOT EXISTS (
        SELECT 1 FROM wmi_filter_consumer_binding binding
        WHERE binding.filter = filter.relative_path
    )
)

SELECT
    ws.status,
    ws.filter_name,
    ws.filter_query,
    ws.query_language,
    ws.filter_class,
    ws.consumer_name,
    ws.consumer_type,
    ws.consumer_class,
    ws.consumer_relative_path,
    ws.command_line_template,
    ws.executable_path,
    ws.scripting_engine,
    ws.script_file_name,
    ws.script_text,
    h.md5,
    h.sha256,
    a.subject_name AS signature_signer,
    a.result AS signature_status
FROM wmi_subscriptions ws
LEFT JOIN hash h ON h.path = ws.hashable_path AND ws.hashable_path != ''
LEFT JOIN authenticode a ON a.path = ws.hashable_path AND ws.hashable_path != ''

Comprehensive osquery query detecting WMI-based persistence:
- Bound subscriptions (active filter-consumer chains)
- Orphaned consumers (residual malware indicators)
- Orphaned filters (incomplete cleanup detection)
- Hash and authenticode enrichment for executables
- Supports CommandLineEventConsumer and ActiveScriptEventConsumer

Uses relative_path JOINs per osquery schema for accurate binding
correlation. ECS mappings included for Elastic Security integration.
- Mark WMI Config & Used Apps (#23) as available
- Mark WMI Providers & Filters (#24) as available
- Add wmi_persistence_event_subscriptions to Additional Queries
- Update coverage statistics: 2/46 artifacts now fully supported (4.3%)
@tomsonpl tomsonpl changed the title Osquery vmi artifact [Osquery_manager] WMI artifacts saved query Dec 3, 2025
@tomsonpl tomsonpl marked this pull request as ready for review December 3, 2025 11:59
@tomsonpl tomsonpl requested a review from a team as a code owner December 3, 2025 11:59
@tomsonpl tomsonpl requested review from ashokaditya and parkiino and removed request for a team December 3, 2025 11:59
@andrewkroh andrewkroh added documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. Integration:osquery_manager Osquery Manager Team:Defend Workflows Security team for Endpoint and OSQuery workflows [elastic/security-defend-workflows] labels Dec 3, 2025
…RE ATT&CK coverage

- Add event.kind, event.module, event.dataset, host.os.type ECS fields
- Add MITRE ATT&CK T1546.003 (Event Triggered Execution: WMI Event Subscription) threat context
- Fix ECS mapping: subject_name→signature_signer for process.code_signature.subject_name
- Remove incorrect threat.indicator.description mapping
- Enhance tags with osquery and windows identifiers
- Fix query column alias: a.subject_name AS signature_signer for proper ECS mapping
- Add ECS mappings for file.created, file.mtime, file.accessed, file.ctime, file.size
- Join with file table to retrieve timestamp metadata for referenced executables/scripts
- Convert timestamps to UTC ISO 8601 format using datetime() for ECS standardization
- Update description to mention file timestamp enrichment
@elasticmachine
Copy link

💚 Build Succeeded

History

Copy link

@calladoum-elastic calladoum-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor comment, otherwise lgtm

"id": "wmi_persistence_event_subscriptions_windows_elastic",
"interval": "3600",
"platform": "windows",
"query": "-- WMI Persistence Event Subscriptions - Complete Coverage (T1546.003)\n-- Detects bound subscriptions and orphaned components for full forensic visibility\n-- Timestamps converted to UTC ISO 8601 format per ECS standardization\n\nWITH wmi_subscriptions AS (\n -- Part 1: Bound subscriptions (complete filter-consumer chains)\n SELECT\n 'bound' AS status,\n filter.name AS filter_name,\n filter.query AS filter_query,\n filter.query_language,\n filter.class AS filter_class,\n COALESCE(cli.name, script.name) AS consumer_name,\n CASE\n WHEN cli.name IS NOT NULL THEN 'CommandLineEventConsumer'\n WHEN script.name IS NOT NULL THEN 'ActiveScriptEventConsumer'\n ELSE 'Unknown'\n END AS consumer_type,\n COALESCE(cli.class, script.class) AS consumer_class,\n COALESCE(cli.relative_path, script.relative_path) AS consumer_relative_path,\n cli.command_line_template,\n cli.executable_path,\n script.scripting_engine,\n script.script_file_name,\n script.script_text,\n COALESCE(\n NULLIF(cli.executable_path, ''),\n NULLIF(cli.command_line_template, ''),\n NULLIF(script.script_file_name, '')\n ) AS hashable_path\n FROM wmi_filter_consumer_binding binding\n LEFT JOIN wmi_event_filters filter ON binding.filter = filter.relative_path\n LEFT JOIN wmi_cli_event_consumers cli ON binding.consumer = cli.relative_path\n LEFT JOIN wmi_script_event_consumers script ON binding.consumer = script.relative_path\n\n UNION ALL\n\n -- Part 2: Orphaned CLI consumers (possible residual malware)\n SELECT\n 'orphaned_consumer' AS status,\n '' AS filter_name,\n '' AS filter_query,\n '' AS query_language,\n '' AS filter_class,\n cli.name AS consumer_name,\n 'CommandLineEventConsumer' AS consumer_type,\n cli.class AS consumer_class,\n cli.relative_path AS consumer_relative_path,\n cli.command_line_template,\n cli.executable_path,\n '' AS scripting_engine,\n '' AS script_file_name,\n '' AS script_text,\n COALESCE(NULLIF(cli.executable_path, ''), NULLIF(cli.command_line_template, '')) AS hashable_path\n FROM wmi_cli_event_consumers cli\n WHERE NOT EXISTS (\n SELECT 1 FROM wmi_filter_consumer_binding binding\n WHERE binding.consumer = cli.relative_path\n )\n\n UNION ALL\n\n -- Part 3: Orphaned Script consumers (possible residual malware)\n SELECT\n 'orphaned_consumer' AS status,\n '' AS filter_name,\n '' AS filter_query,\n '' AS query_language,\n '' AS filter_class,\n script.name AS consumer_name,\n 'ActiveScriptEventConsumer' AS consumer_type,\n script.class AS consumer_class,\n script.relative_path AS consumer_relative_path,\n '' AS command_line_template,\n '' AS executable_path,\n script.scripting_engine,\n script.script_file_name,\n script.script_text,\n NULLIF(script.script_file_name, '') AS hashable_path\n FROM wmi_script_event_consumers script\n WHERE NOT EXISTS (\n SELECT 1 FROM wmi_filter_consumer_binding binding\n WHERE binding.consumer = script.relative_path\n )\n\n UNION ALL\n\n -- Part 4: Orphaned filters (possible residual malware)\n SELECT\n 'orphaned_filter' AS status,\n filter.name AS filter_name,\n filter.query AS filter_query,\n filter.query_language,\n filter.class AS filter_class,\n '' AS consumer_name,\n '' AS consumer_type,\n '' AS consumer_class,\n '' AS consumer_relative_path,\n '' AS command_line_template,\n '' AS executable_path,\n '' AS scripting_engine,\n '' AS script_file_name,\n '' AS script_text,\n '' AS hashable_path\n FROM wmi_event_filters filter\n WHERE NOT EXISTS (\n SELECT 1 FROM wmi_filter_consumer_binding binding\n WHERE binding.filter = filter.relative_path\n )\n)\n\nSELECT\n ws.status,\n ws.filter_name,\n ws.filter_query,\n ws.query_language,\n ws.filter_class,\n ws.consumer_name,\n ws.consumer_type,\n ws.consumer_class,\n ws.consumer_relative_path,\n ws.command_line_template,\n ws.executable_path,\n ws.scripting_engine,\n ws.script_file_name,\n ws.script_text,\n h.md5,\n h.sha256,\n a.subject_name AS signature_signer,\n a.result AS signature_status,\n -- File timestamps converted to UTC ISO 8601 with readable aliases (ECS standard)\n datetime(f.btime, 'unixepoch') AS created_time,\n datetime(f.mtime, 'unixepoch') AS modified_time,\n datetime(f.atime, 'unixepoch') AS accessed_time,\n datetime(f.ctime, 'unixepoch') AS changed_time,\n f.size AS file_size\nFROM wmi_subscriptions ws\nLEFT JOIN hash h ON h.path = ws.hashable_path AND ws.hashable_path != ''\nLEFT JOIN authenticode a ON a.path = ws.hashable_path AND ws.hashable_path != ''\nLEFT JOIN file f ON f.path = ws.hashable_path AND ws.hashable_path != ''",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

h.sha1 is missing. Is that on purpose?
#16059 exposed the 3 (i.e. md5/sha1/sha256)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. Integration:osquery_manager Osquery Manager Team:Defend Workflows Security team for Endpoint and OSQuery workflows [elastic/security-defend-workflows]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants