IoC (Indicator of Compromise) Investigation - Instructions
Purpose
This skill performs comprehensive security investigations on Indicators of Compromise (IoCs) including:
- IP Addresses: Network connections, threat intel matches, geographic analysis, organizational exposure
- DNS Domains: Domain reputation, connection events, email-based threats, URL analysis
- URLs: URL reputation, phishing detection, email delivery, browser activity
- File Hashes: Malware analysis, file prevalence, related alerts, affected devices
The investigation correlates IoCs with Microsoft Defender Threat Intelligence, identifies associated CVEs, and enumerates organizational assets affected by those vulnerabilities.
📑 TABLE OF CONTENTS
- Critical Workflow Rules - Start here!
- Investigation Types - By IoC type
- Quick Start - 5-step investigation pattern
- Execution Workflow - Complete process
- Sample KQL Queries - Validated query patterns
- Defender API Queries - Threat Intel & Vulnerability Management
- JSON Export Structure - Required fields
- Error Handling - Troubleshooting guide
⚠️ CRITICAL WORKFLOW RULES - READ FIRST ⚠️
Before starting ANY IoC investigation:
- ALWAYS identify the IoC type FIRST (IP, Domain, URL, or File Hash)
- ALWAYS normalize the IoC (lowercase domains, validate IP format, extract domain from URL)
- ALWAYS calculate date ranges correctly (use current date from context - see Date Range section)
- ALWAYS track and report time after each major step (mandatory)
- ALWAYS run independent queries in parallel (drastically faster execution)
- ALWAYS use
create_filefor JSON export (NEVER use PowerShell terminal commands) - ⛔ ALWAYS enforce Sentinel workspace selection (see Workspace Selection section below)
⛔ MANDATORY: Sentinel Workspace Selection
This skill requires a Sentinel workspace to execute queries. Follow these rules STRICTLY:
When invoked from incident-investigation skill:
- Inherit the workspace selection from the parent investigation context
- If no workspace was selected in parent context: STOP and ask user to select
- Use the
SELECTED_WORKSPACE_IDSpassed from the parent skill
When invoked standalone (direct user request):
- ALWAYS call
list_sentinel_workspacesMCP tool FIRST - If 1 workspace exists: Auto-select, display to user, proceed
- If multiple workspaces exist:
- Display all workspaces with Name and ID
- ASK: "Which Sentinel workspace should I use for this investigation?"
- ⛔ STOP AND WAIT for user response
- ⛔ DO NOT proceed until user explicitly selects
- If a query fails on the selected workspace:
- ⛔ DO NOT automatically try another workspace
- STOP and report the error
- Display available workspaces
- ASK user to select a different workspace
- WAIT for user response
Workspace Failure Handling
IF query returns "Failed to resolve table" or similar error:
- STOP IMMEDIATELY
- Report: "⚠️ Query failed on workspace [NAME] ([ID]). Error: [ERROR_MESSAGE]"
- Display: "Available workspaces: [LIST_ALL_WORKSPACES]"
- ASK: "Which workspace should I use instead?"
- WAIT for explicit user response
- DO NOT retry with a different workspace automatically
🔴 PROHIBITED ACTIONS:
- ❌ Selecting a workspace without user consent when multiple exist
- ❌ Switching to another workspace after a failure without asking
- ❌ Proceeding with investigation if workspace selection is ambiguous
- ❌ Assuming a workspace based on previous sessions
IoC Type Detection Rules:
| Pattern | IoC Type | Normalization |
|---|---|---|
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} |
IPv4 Address | Validate octets ≤255 |
[a-fA-F0-9:]+ (with multiple colons) |
IPv6 Address | Lowercase, expand if needed |
[a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z]{2,} |
Domain | Lowercase, remove trailing dot |
https?://.* or starts with www. |
URL | Extract domain for separate analysis |
| 32 hex chars | MD5 Hash | Lowercase |
| 40 hex chars | SHA1 Hash | Lowercase |
| 64 hex chars | SHA256 Hash | Lowercase |
Date Range Rules:
- Real-time/recent searches: Add +2 days to current date for end range
- Historical ranges: Add +1 day to user's specified end date
- Example: Current date = Jan 23; "Last 7 days" →
datetime(2026-01-16)todatetime(2026-01-25)
Available Investigation Types
IP Address Investigation
When to use: Suspicious inbound/outbound connections, firewall alerts, sign-in anomalies
Example prompts:
- "Investigate IP 203.0.113.42"
- "Is 198.51.100.10 malicious?"
- "Check threat intel for 192.0.2.1"
Data sources:
- Defender Threat Intelligence (IP alerts, statistics)
- DeviceNetworkEvents (connection history)
- ThreatIntelIndicators (Sentinel TI table)
- SigninLogs (if used for authentication)
- Defender IOC list (custom indicators)
enrich_ips.py(3rd-party enrichment: ipinfo.io geo/ISP, vpnapi.io VPN/proxy/Tor, AbuseIPDB abuse score & reports, Shodan ports/services/CVEs/tags)
Domain Investigation
When to use: Suspicious DNS queries, phishing domains, C2 communication
Example prompts:
- "Investigate domain malware-c2.example.com"
- "Is evil.com in our threat intel?"
- "Check if any devices connected to suspicious.net"
Data sources:
- DeviceNetworkEvents (DNS queries, HTTP connections)
- EmailUrlInfo (email-delivered URLs)
- ThreatIntelIndicators (domain indicators)
- Defender IOC list (blocked domains)
- UrlClickEvents (user clicks on domain)
URL Investigation
When to use: Phishing links, malicious downloads, suspicious redirects
Example prompts:
- "Investigate URL https://phishing.example.com/login"
- "Was this URL clicked by anyone?"
- "Check threat intel for http://malware.site/payload.exe"
Data sources:
- EmailUrlInfo (URLs in emails)
- UrlClickEvents (click tracking)
- DeviceNetworkEvents (HTTP/HTTPS connections)
- DeviceFileEvents (downloads from URL)
- ThreatIntelIndicators (URL patterns)
File Hash Investigation
When to use: Malware analysis, suspicious executables, file reputation
Example prompts:
- "Investigate hash a1b2c3d4e5f6..."
- "Is this SHA256 known malware?"
- "Which devices have this file?"
Data sources:
- Defender File Info & Statistics
- Defender File Alerts
- Defender File Related Machines
- DeviceFileEvents (file creation/modification)
- ThreatIntelIndicators (file hash indicators)
Quick Start (TL;DR)
When a user requests an IoC investigation:
Identify & Normalize IoC:
- Detect IoC type (IP/Domain/URL/Hash) - Normalize format (lowercase, validate) - Extract embedded IoCs (domain from URL)Run Parallel Queries (Batch 1 - Threat Intel):
- Sentinel ThreatIntelIndicators query
- Defender Indicators lookup (ListDefenderIndicators)
- Defender IP/File alerts (GetDefenderIpAlerts or GetDefenderFileAlerts)
- Defender IP/File statistics
Run 3rd-Party IP Enrichment (IP IoCs only):
python enrich_ips.py <IP_ADDRESS>- ipinfo.io: Geolocation, ISP/ASN, hosting provider
- vpnapi.io: VPN, proxy, Tor exit node detection
- AbuseIPDB: Abuse confidence score, recent attack reports
- Shodan: Open ports, services/banners, CVEs, tags (e.g.,
c2,eol-os,self-signed)
Run Parallel Queries (Batch 2 - Activity):
- DeviceNetworkEvents (connections involving IoC)
- AlertEvidence (alerts with IoC as evidence)
- SecurityAlert (alerts mentioning IoC)
- EmailUrlInfo (if domain/URL)
CVE & Vulnerability Correlation:
- Extract CVE IDs from threat intel results AND Shodan enrichment
- For each CVE: ListDefenderMachinesByVulnerability
- Aggregate affected devices
Export to JSON & Generate Summary:
temp/ioc_investigation_{ioc_normalized}_{timestamp}.json
Execution Workflow
🚨 MANDATORY: Time Tracking Pattern
YOU MUST TRACK AND REPORT TIME AFTER EVERY MAJOR STEP:
[MM:SS] ✓ Step description (XX seconds)
Required Reporting Points:
- After IoC normalization and type detection
- After 3rd-party IP enrichment (IP IoCs)
- After Defender/Sentinel threat intelligence lookup
- After activity/connection analysis
- After CVE correlation and device enumeration
- After JSON file creation
- Final: Total elapsed time
Phase 1: IoC Identification and Normalization (REQUIRED FIRST)
Step 1.1: Detect IoC Type
# Regex patterns for IoC detection
IPv4: r'^(\d{1,3}\.){3}\d{1,3}$'
IPv6: r'^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$'
Domain: r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
URL: r'^https?://'
MD5: r'^[a-fA-F0-9]{32}$'
SHA1: r'^[a-fA-F0-9]{40}$'
SHA256: r'^[a-fA-F0-9]{64}$'
Step 1.2: Normalize IoC
- IP Address: Validate octets, detect IPv4 vs IPv6
- Domain: Lowercase, remove trailing dots, extract from URL if needed
- URL: Keep full URL, also extract domain for parallel investigation
- Hash: Lowercase
Step 1.3: Create Investigation Context
{
"ioc_type": "ip|domain|url|hash",
"ioc_value": "<normalized_value>",
"ioc_original": "<user_provided_value>",
"extracted_domain": "<if_url>",
"investigation_start": "<timestamp>",
"date_range_start": "<StartDate>",
"date_range_end": "<EndDate>"
}
Phase 2: 3rd-Party IP Enrichment (IP Address IoCs)
MANDATORY for all IP address investigations. Run enrich_ips.py to get external threat intelligence context that is NOT available from Defender/Sentinel native tools.
python enrich_ips.py <IP_ADDRESS_1> <IP_ADDRESS_2> ...
What it provides:
| Source | Intelligence |
|---|---|
| ipinfo.io | Geolocation (city, country, coordinates), ISP/ASN, organization, hosting provider detection |
| vpnapi.io | VPN, proxy, Tor exit node, relay detection |
| AbuseIPDB | Abuse confidence score (0-100), total reports, last reported date, recent reporter comments with attack categories |
| Shodan | Open ports, service/banner details, OS detection, known CVEs, tags (e.g., c2, eol-os, self-signed, honeypot), CPEs, hostnames |
Output: Per-IP detailed results printed to terminal + JSON export saved to temp/.
Integration with investigation:
- AbuseIPDB score ≥ 75: 🔴 Strong indicator of malicious activity — flag as high risk
- VPN/Proxy/Tor detected: 🟠 Potential evasion — note in risk assessment
- Shodan tags contain
c2: 🔴 Known C2 infrastructure — escalate immediately - Shodan CVEs found: Cross-reference with Phase 5 CVE correlation for organizational exposure
- Hosting provider (not residential ISP): 🟡 May indicate attacker infrastructure
Note: For domain and URL IoCs, extract the resolved IP(s) from DeviceNetworkEvents results and run enrichment on those IPs as a follow-up step.
Phase 3: Parallel Threat Intelligence Collection (Defender & Sentinel)
CRITICAL: Run ALL threat intel queries in parallel for speed!
Batch 1: Threat Intelligence APIs (Run ALL in parallel)
| Query | Tool/API | IoC Types |
|---|---|---|
| Defender IOC List | ListDefenderIndicators ⚠️ |
IP, Domain, URL |
| Defender IP Alerts | GetDefenderIpAlerts |
IP |
| Defender IP Statistics | GetDefenderIpStatistics |
IP |
| Defender File Alerts | GetDefenderFileAlerts |
Hash |
| Defender File Info | GetDefenderFileInfo |
Hash |
| Defender File Statistics | GetDefenderFileStatistics |
Hash |
| Defender File Machines | GetDefenderFileRelatedMachines |
Hash |
⚠️ ListDefenderIndicators Note: If result is written to file (>50KB), you MUST read and filter the file manually. See Custom IOC Management for required processing steps.
Batch 2: Sentinel KQL Queries (Run ALL in parallel)
| Query | Table | IoC Types |
|---|---|---|
| TI Indicators Match | ThreatIntelIndicators | All |
| Network Connections | DeviceNetworkEvents | IP, Domain, URL |
| Alert Evidence | AlertEvidence | All |
| Security Alerts | SecurityAlert | All |
| Email URLs | EmailUrlInfo | Domain, URL |
Phase 4: CVE Correlation and Vulnerability Management
Step 4.1: Extract CVE IDs from Threat Intel AND Enrichment
- Parse threat intel results for CVE references (pattern:
CVE-\d{4}-\d{4,}) - Extract from: alert descriptions, threat family info, MITRE techniques
- Extract from Shodan enrichment (
shodan_vulnsfield fromenrich_ips.pyoutput)
Step 4.2: Query Affected Devices per CVE
For each CVE_ID found:
→ ListDefenderMachinesByVulnerability(cveId: CVE_ID)
→ Collect: deviceId, deviceName, osPlatform, exposureLevel
Step 4.3: Aggregate Device Exposure
{
"cve_correlation": {
"cve_ids_found": ["CVE-2024-1234", "CVE-2024-5678"],
"affected_devices_by_cve": {
"CVE-2024-1234": [
{"deviceId": "...", "deviceName": "...", "osPlatform": "..."}
]
},
"total_unique_affected_devices": 15,
"critical_cves": 2,
"high_cves": 3
}
}
Phase 5: Activity and Connection Analysis
For IP Address IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let IPAddress = '<IP_ADDRESS>';
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == IPAddress or LocalIP == IPAddress
| summarize
ConnectionCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Ports = make_set(RemotePort),
Protocols = make_set(Protocol)
by ActionType
| order by ConnectionCount desc
For Domain IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let Domain = '<DOMAIN>';
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has Domain
| summarize
ConnectionCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
UniqueURLs = make_set(RemoteUrl, 10)
by DeviceName
| order by ConnectionCount desc
For File Hash IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let Hash = '<HASH>';
union withsource=SourceTable DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents, DeviceEvents
| where Timestamp between (start .. end)
| where SHA1 =~ Hash or SHA256 =~ Hash or MD5 =~ Hash or InitiatingProcessSHA256 =~ Hash
| summarize
EventCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
FileNames = make_set(FileName),
FolderPaths = make_set(FolderPath, 5)
by ActionType
| order by EventCount desc
Phase 6: Export to JSON
Create single JSON file: temp/ioc_investigation_{ioc_type}_{ioc_normalized}_{timestamp}.json
Sample KQL Queries
Use these exact patterns with appropriate MCP tools. Replace <IOC_VALUE>, <StartDate>, <EndDate>.
⚠️ CRITICAL: START WITH THESE EXACT QUERY PATTERNS These queries have been tested and validated. Use them as your PRIMARY reference.
📅 Date Range Quick Reference
🔴 STEP 0: GET CURRENT DATE FIRST (MANDATORY) 🔴
- ALWAYS check the current date from the context header BEFORE calculating date ranges
- NEVER use hardcoded years - the year changes and you WILL query the wrong timeframe
RULE 1: Real-Time/Recent Searches (Current Activity)
- Add +2 days to current date for end range
- Why +2? +1 for timezone offset + +1 for inclusive end-of-day
- Pattern: Today is Jan 23 → Use
datetime(2026-01-25)as end date
RULE 2: Historical Searches (User-Specified Dates)
- Add +1 day to user's specified end date
- Why +1? To include all 24 hours of the final day
1. Threat Intelligence Indicator Match (Sentinel - limited to first 20 IoCs)
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let ioc_value = '<IOC_VALUE>';
ThreatIntelIndicators
| where TimeGenerated between (start .. end)
| where IsActive == true and IsDeleted == false
| summarize arg_max(TimeGenerated, *) by Id
| where ObservableValue =~ ioc_value
or Pattern has ioc_value
| project
TimeGenerated,
Id,
ObservableKey,
ObservableValue,
Pattern,
Confidence,
ValidFrom,
ValidUntil,
Tags,
Data
| order by TimeGenerated desc
| take 20
2. IP Address - Network Connection Activity (Advanced Hunting)
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == target_ip or LocalIP == target_ip
| extend Direction = iff(RemoteIP == target_ip, "Outbound", "Inbound")
| summarize
TotalConnections = count(),
UniqueDevices = dcount(DeviceId),
UniquePorts = dcount(RemotePort),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
Ports = make_set(RemotePort, 20),
Protocols = make_set(Protocol),
ActionTypes = make_set(ActionType),
InitiatingProcesses = make_set(InitiatingProcessFileName, 10),
Direction = make_set(Direction,2)
3. IP Address - Detailed Connection Timeline (limited to first 20 events)
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == target_ip or LocalIP == target_ip
| project
Timestamp,
DeviceName,
DeviceId,
ActionType,
RemoteIP,
RemotePort,
RemoteUrl,
LocalIP,
LocalPort,
Protocol,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
InitiatingProcessAccountName
| order by Timestamp desc
| take 20
4. Domain - DNS and HTTP Connection Activity
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has target_domain
| summarize
TotalConnections = count(),
UniqueDevices = dcount(DeviceId),
UniqueUsers = dcount(InitiatingProcessAccountName),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
URLs = make_set(RemoteUrl, 20),
Ports = make_set(RemotePort),
InitiatingProcesses = make_set(InitiatingProcessFileName, 10)
5. Domain - Detailed Connection Timeline (limited to first 20 events)
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has target_domain
| project
Timestamp,
DeviceName,
InitiatingProcessAccountName,
ActionType,
RemoteUrl,
RemoteIP,
RemotePort,
Protocol,
InitiatingProcessFileName,
InitiatingProcessCommandLine
| order by Timestamp desc
| take 20
6. URL - Email Delivery Analysis
let target_url = '<URL>';
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
EmailUrlInfo
| where TimeGenerated between (start .. end)
| where Url == target_url or Url has target_domain or UrlDomain =~ target_domain
| summarize
EmailCount = dcount(NetworkMessageId),
UniqueURLs = make_set(Url, 10),
UrlLocations = make_set(UrlLocation),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by UrlDomain
| order by EmailCount desc
7. File Hash - Device File Events
let target_hash = '<HASH>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
union withsource=SourceTable DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents, DeviceEvents
| where Timestamp between (start .. end)
| where SHA1 =~ target_hash or SHA256 =~ target_hash or MD5 =~ target_hash or InitiatingProcessSHA256 =~ target_hash
| summarize
EventCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
FileNames = make_set(FileName, 10),
FolderPaths = make_set(FolderPath, 10),
ActionTypes = make_set(ActionType)
| extend HashType = case(
isnotempty(target_hash) and strlen(target_hash) == 32, "MD5",
isnotempty(target_hash) and strlen(target_hash) == 40, "SHA1",
isnotempty(target_hash) and strlen(target_hash) == 64, "SHA256",
"Unknown")
8. Alert Evidence - IoC in Alerts (limited to first 20 alerts)
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| project
TimeGenerated,
AlertId,
Title,
Severity,
Categories,
ServiceSource,
EntityType,
EvidenceRole,
RemoteIP,
RemoteUrl,
FileName,
SHA1,
SHA256,
DeviceName,
AccountName
| order by TimeGenerated desc
| take 20
9. Security Alerts Mentioning IoC
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| join AlertInfo on AlertId
| extend HostFullName = strcat(parse_json(parse_json(AdditionalFields).Host).HostName,".", parse_json(parse_json(AdditionalFields).Host).DnsDomain)
| extend OS = strcat(parse_json(parse_json(AdditionalFields).Host).OSFamily," ", parse_json(parse_json(AdditionalFields).Host).OSVersion)
| extend IsDomainJoined = parse_json(parse_json(AdditionalFields).Host).IsDomainJoined
| extend AffectedDevice = strcat(HostFullName,",", OS, ",IsDomainJoined: ", IsDomainJoined)
| summarize
AlertCount = dcount(AlertId),
Alerts = make_set(Title, 10),
Severities = make_set(Severity),
Categories = make_set(Category),
AttackTechniques = make_set(AttackTechniques),
AffectedDevices = make_set(AffectedDevice, 10)
10. Defender Custom IOC List Match
// Use Defender API: ListDefenderIndicators with filters
// indicatorType: "IpAddress" | "DomainName" | "Url" | "FileSha1" | "FileSha256" | "FileMd5"
// indicatorValue: "<IOC_VALUE>"
11. IP Address - Sign-in Analysis (Azure AD)
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated between (start .. end)
| where IPAddress == target_ip
| summarize
SignInCount = count(),
UniqueUsers = dcount(UserPrincipalName),
SuccessCount = countif(ResultType == '0'),
FailureCount = countif(ResultType != '0'),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
Users = make_set(UserPrincipalName, 10),
Apps = make_set(AppDisplayName, 10),
ResultTypes = make_set(ResultType)
| extend SuccessRate = round(100.0 * SuccessCount / SignInCount, 2)
12. CVE Extraction from Alerts
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| extend CVEs = extract_all(@"(CVE-\d{4}-\d{4,})", tostring(AttackTechniques))
| mv-expand CVE = CVEs
| where isnotempty(CVE)
| summarize
CVECount = dcount(tostring(CVE)),
CVEs = make_set(tostring(CVE)),
AlertCount = dcount(AlertId),
Alerts = make_set(Title, 5)
Defender API Queries
IP Address Investigation
Get Alerts for IP:
Tool: GetDefenderIpAlerts (MCP)
Parameter: ipAddress = "<IP_ADDRESS>"
Returns: All security alerts associated with the IP
Get IP Statistics:
Tool: activate_file_and_ip_statistics_tools → GetDefenderIpStatistics
Parameter: ipAddress = "<IP_ADDRESS>"
Returns: Organization prevalence, device count, communication stats
Find Devices by IP:
Tool: FindDefenderMachinesByIp (MCP)
Parameters:
ipAddress = "<IP_ADDRESS>"
timestamp = "<DATETIME>" (ISO 8601 format)
Returns: Devices that communicated with IP ±15 minutes of timestamp
File Hash Investigation
Get File Info:
Tool: activate_file_and_ip_statistics_tools → GetDefenderFileInfo
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: File details, global prevalence, threat determination
Get File Statistics:
Tool: activate_file_and_ip_statistics_tools → GetDefenderFileStatistics
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: Organization statistics, device count, global stats
Get File Alerts:
Tool: GetDefenderFileAlerts (MCP)
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: All alerts associated with the file
Get Devices with File:
Tool: GetDefenderFileRelatedMachines (MCP)
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: All devices where file was observed
Vulnerability Management
List Devices Affected by CVE:
Tool: ListDefenderMachinesByVulnerability (MCP)
Parameter: cveId = "CVE-YYYY-NNNNN"
Returns: All devices vulnerable to the CVE with exposure details
Get Device Vulnerabilities:
Tool: GetDefenderMachineVulnerabilities (MCP)
Parameter: id = "<DEVICE_ID>"
Returns: All CVEs affecting the specific device
Custom IOC Management
Search Existing IOCs:
Tool: ListDefenderIndicators (MCP)
Parameters (all optional):
indicatorType = "IpAddress" | "DomainName" | "Url" | "FileSha1" | "FileSha256" | "FileMd5"
indicatorValue = "<IOC_VALUE>"
action = "Alert" | "Block" | "Allow"
severity = "Informational" | "Low" | "Medium" | "High"
Returns: Matching custom indicators in tenant
⚠️ CRITICAL: Processing Large ListDefenderIndicators Results
The ListDefenderIndicators API may return ALL custom indicators in the tenant regardless of filter parameters. When results are large (>50KB), they are written to a temporary file instead of returned inline.
MANDATORY Processing Steps:
If result says "Large tool result written to file":
- Use
read_filetool to read the content file path provided - Parse the JSON response to extract the
valuearray - Manually filter for the target IoC using case-insensitive matching:
# Filter logic for IP address matches = [ind for ind in indicators["value"] if ind.get("indicatorValue", "").lower() == target_ioc.lower()] - Report: "Found X custom indicator(s) matching [IOC]" or "No custom indicators match [IOC]"
- Use
If result is inline JSON with empty
valuearray:- Report: "No custom indicators found for [IOC]"
🔴 PROHIBITED:
- ❌ Assuming "large result = no match" without reading and filtering the file
- ❌ Reporting "Not in IOC list" without verifying the actual content
- ❌ Skipping file processing due to result size
Example - Correct Processing:
1. Call: ListDefenderIndicators(indicatorType: "IpAddress", indicatorValue: "203.0.113.42")
2. Result: "Large tool result (69KB) written to file: /path/to/content.json"
3. Action: read_file(/path/to/content.json)
4. Parse: Extract value array from JSON
5. Filter: Search for indicatorValue == "203.0.113.42" (case-insensitive)
6. Report: "No custom indicators match 203.0.113.42" OR "Found 1 custom indicator: [details]"
JSON Export Structure
Create file: temp/ioc_investigation_{ioc_type}_{ioc_normalized}_{timestamp}.json
{
"investigation_metadata": {
"ioc_type": "ip|domain|url|hash",
"ioc_value": "<normalized_value>",
"ioc_original": "<user_input>",
"investigation_timestamp": "<ISO8601>",
"date_range_start": "<StartDate>",
"date_range_end": "<EndDate>",
"elapsed_time_seconds": 45
},
"threat_intelligence": {
"sentinel_ti_matches": [],
"defender_ioc_matches": [],
"defender_alerts": [],
"threat_families": [],
"confidence_score": 0-100,
"verdict": "Malicious|Suspicious|Clean|Unknown"
},
"ip_enrichment": {
"geo": { "city": "", "country": "", "org": "", "isp": "" },
"vpn_proxy_tor": { "is_vpn": false, "is_proxy": false, "is_tor": false },
"abuseipdb": { "abuse_confidence_score": 0, "total_reports": 0, "last_reported": "", "recent_categories": [] },
"shodan": { "ports": [], "services": [], "vulns": [], "tags": [], "os": "", "hostnames": [], "cpes": [] }
},
"activity_analysis": {
"network_connections": {
"total_connections": 0,
"unique_devices": 0,
"unique_users": 0,
"first_seen": "<datetime>",
"last_seen": "<datetime>",
"top_devices": [],
"top_ports": [],
"top_processes": []
},
"email_delivery": {
"email_count": 0,
"unique_urls": [],
"delivery_locations": []
},
"file_activity": {
"event_count": 0,
"unique_devices": 0,
"file_names": [],
"folder_paths": [],
"action_types": []
},
"signin_activity": {
"signin_count": 0,
"unique_users": 0,
"success_rate": 0,
"affected_users": []
}
},
"alert_correlation": {
"total_alerts": 0,
"severity_breakdown": {
"high": 0,
"medium": 0,
"low": 0,
"informational": 0
},
"alert_titles": [],
"attack_techniques": [],
"affected_entities": []
},
"cve_correlation": {
"cve_ids_found": [],
"affected_devices_by_cve": {},
"total_unique_affected_devices": 0,
"cve_severity_breakdown": {
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
}
},
"organizational_exposure": {
"total_affected_devices": 0,
"affected_device_list": [],
"exposure_level": "High|Medium|Low|None",
"recommended_actions": []
},
"risk_assessment": {
"overall_risk": "Critical|High|Medium|Low|Informational",
"risk_factors": [],
"mitigating_factors": [],
"confidence": "High|Medium|Low"
}
}
Error Handling
Common Issues and Solutions
| Issue | Solution |
|---|---|
| No TI matches found | IoC may be unknown; proceed with activity analysis |
| Defender API returns 404 | IoC not in organization's scope; check Sentinel data |
| Empty DeviceNetworkEvents | Expand date range or check if MDE is deployed |
| CVE not found in vulnerability DB | CVE may be too new or not applicable to org assets |
| Multiple IoC types detected | Investigate each separately, correlate results |
| Rate limiting on API calls | Add delays between API calls, batch where possible |
| ListDefenderIndicators returns large file | Read file with read_file, parse JSON, manually filter for target IoC value |
Required Field Defaults
If queries return no results, use these defaults:
{
"threat_intelligence": {
"sentinel_ti_matches": [],
"defender_alerts": [],
"verdict": "Unknown",
"confidence_score": 0
},
"activity_analysis": {
"network_connections": {
"total_connections": 0,
"unique_devices": 0
}
},
"cve_correlation": {
"cve_ids_found": [],
"affected_devices_by_cve": {},
"total_unique_affected_devices": 0
}
}
Example Workflows
Example 1: IP Address Investigation
User says: "Investigate IP 203.0.113.42 for the last 7 days"
Workflow:
- Identify IoC: IPv4 Address, normalized:
203.0.113.42 - 3rd-Party Enrichment:
→ Get geo, ISP, VPN/proxy/Tor flags, AbuseIPDB score, Shodan ports/CVEs/tagspython enrich_ips.py 203.0.113.42 - Phase 1 - Threat Intel (parallel):
GetDefenderIpAlerts(ipAddress: "203.0.113.42")- Sentinel ThreatIntelIndicators query
ListDefenderIndicators(indicatorType: "IpAddress", indicatorValue: "203.0.113.42")
- Phase 2 - Activity Analysis (parallel):
- DeviceNetworkEvents query for IP
- SigninLogs query for IP
- AlertEvidence query for IP
- Phase 3 - CVE Correlation:
- Extract CVEs from alerts AND Shodan enrichment
- For each CVE:
ListDefenderMachinesByVulnerability
- Export JSON and summarize findings (include enrichment data in JSON export)
Example 2: Domain Investigation
User says: "Is evil-malware.com in our environment?"
Workflow:
- Identify IoC: Domain, normalized:
evil-malware.com - Phase 1 - Threat Intel (parallel):
- Sentinel ThreatIntelIndicators query
ListDefenderIndicators(indicatorType: "DomainName", indicatorValue: "evil-malware.com")
- Phase 2 - Activity Analysis (parallel):
- DeviceNetworkEvents query for domain
- EmailUrlInfo query for domain
- AlertEvidence query for domain
- Phase 3 - Exposure Assessment:
- List all devices that connected
- Identify affected users
- Export JSON and summarize findings
Example 3: File Hash Investigation with CVE Correlation
User says: "Investigate SHA256 a1b2c3... and check which devices are vulnerable"
Workflow:
- Identify IoC: SHA256 Hash, normalized:
a1b2c3... - Phase 1 - Threat Intel (parallel):
GetDefenderFileInfo(fileHash: "a1b2c3...")GetDefenderFileAlerts(fileHash: "a1b2c3...")GetDefenderFileStatistics(fileHash: "a1b2c3...")
- Phase 2 - Device Exposure:
GetDefenderFileRelatedMachines(fileHash: "a1b2c3...")- DeviceFileEvents query
- Phase 3 - CVE Correlation:
- Extract CVEs from file threat family info
- For each CVE:
ListDefenderMachinesByVulnerability - Cross-reference with devices that have the file
- Export JSON and summarize with remediation priorities
Security Notes
- All investigations are logged for audit purposes
- IoC values may be sensitive - handle with care
- Follow organizational data classification policies
- Consider threat actor attribution implications
- Document investigation actions for incident timeline
Integration with Other Skills
This skill can be combined with:
- user-investigation: When IoC is found in user's sign-in logs
- computer-investigation: When IoC is found on specific device
- authentication-tracing: When IoC IP appears in auth anomalies
- ca-policy-investigation: When IoC triggers conditional access events
Cross-skill pivot example: "Investigate IP 203.0.113.42" → Found in user sign-ins → "Investigate user@domain.com" using user-investigation skill
Converted and distributed by TomeVault — claim your Tome and manage your conversions.