Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions detection/snowflake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,33 @@ is the biggest deployment lift; the [`enrichment-templates/`](enrichment-templat
directory has the SQL and SIEM lookup definitions to compute the derived
fields.

### DEPLOYMENT_BLOCKED by default

The following rules will **silently not fire** if loaded into a SIEM
without the listed prerequisite. A SOC that ships the pack and assumes
the rules are healthy will accumulate detection debt; treat this list
as a gating checklist, not as a footnote.

| Rule | Blocked by | How to unblock |
|------|-----------|----------------|
| [`cortex_agent_directive_followup.yml`](../../tools/llm-attacks/cortex/detection/sigma/cortex_agent_directive_followup.yml) | `cortex_sidecar` | Trail `cortex_agent.*` event family **or** install the Snowpark wrapper at [`tools/llm-attacks/cortex/lab-validation/observe_cortex_agent_trace.sql`](../../tools/llm-attacks/cortex/lab-validation/observe_cortex_agent_trace.sql). |
| [`cortex_agent_followup_without_user_intent.yml`](../../tools/llm-attacks/cortex/detection/sigma/cortex_agent_followup_without_user_intent.yml) | `cortex_sidecar` + `OPS.SECURITY.AGENT_TOOL_CHAIN_ALLOWLIST` | Same wrapper as above, plus a SIEM-side lookup populated from the agent registry. |
| [`cortex_agent_sql_from_tool_output.yml`](../../tools/llm-attacks/cortex/detection/sigma/cortex_agent_sql_from_tool_output.yml) | `cortex_sidecar` (`sql_origin` tag) | Wrapper tags each executed query with `user_prompt` / `tool_output` / `planner`. |
| [`cortex_search_rank_anomaly.yml`](../../tools/llm-attacks/cortex/detection/sigma/cortex_search_rank_anomaly.yml) | `cortex_sidecar` (per-query top-N capture) | Wrapper records `rank_at_search_time` per (document, query) pair. |
| [`cortex_agent_directive_followup_trail.yml`](../../tools/llm-attacks/cortex/detection/sigma/cortex_agent_directive_followup_trail.yml) | Trail event family | `cortex_agent.step_completed` enabled in the Trail subscription. |
| [`federated_login_anomaly.yml`](sigma/federated_login_anomaly.yml) | IdP audit ingest + correlation | Templates at [`enrichment-templates/idp-okta-system-log/`](enrichment-templates/idp-okta-system-log/) and [`enrichment-templates/idp-entra-signin/`](enrichment-templates/idp-entra-signin/). |
| [`oauth_integration_scope_drift.yml`](../../tools/cloud-identity/snowflake/detection/sigma/oauth_integration_scope_drift.yml) | IdP consent snapshot | Daily snapshot of `OPS.SECURITY.IDP_CONSENT_SNAPSHOT_DAILY` via Okta `/api/v1/apps/{id}/grants` or Entra `oauth2PermissionGrants`. |
| [`snowflake_scim_role_race.yml`](../../tools/cloud-identity/snowflake/detection/sigma/snowflake_scim_role_race.yml) | SCIM-side audit ingest | Okta SCIM logs or Entra Provisioning Logs joined to Snowflake SCIM PATCH stream. |
| [`cortex_code_session_to_unknown_session.yml`](sigma/cortex_code_session_to_unknown_session.yml) | EDR ingest + `OPS.SECURITY.HOST_EGRESS_RANGES` | EDR process-creation events for the Cortex Code CLI binary; host-egress map from VPN or device-posture data. |
| [`cortex_code_pre_1_0_25.yml`](sigma/cortex_code_pre_1_0_25.yml) | Endpoint EDR `command_line` | Capture the CLI version string from process-creation telemetry. |

Interim coverage when the prerequisite is not yet available is documented
inline on the report's [Detection surface page](../../reports/snowflake-platform-assessment/detection.html);
the gist is that most prerequisites have a policy-layer compensating
control (network policy on federated users, row-access policies for
Cortex Agents, package-manager gates for the CLI version check) that
covers the same risk while the dependency is being wired up.

## Per-chain mapping

Every chain has both an ACCOUNT_USAGE-shaped rule (for the audit-table
Expand Down
18 changes: 17 additions & 1 deletion detection/snowflake/enrichment-templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,30 @@ Sigma rules in this pack depend on. Without these, the
`requires_enrichment` and `requires_correlation` rules silently do not
fire — they are not SIEM syntax errors, they are deployment gaps.

The templates cover the three highest-value rules:
The templates cover the highest-value rules. Templates are organised by
shape: detection-side enrichment templates produce derived fields for
a single Sigma rule; IdP-side templates land the upstream audit feed
that several rules then correlate against.

### Detection-side enrichment templates

| Template directory | Rule it enables | Maturity | Why it's load-bearing |
|--------------------|------------------|----------|------------------------|
| [`bulk-exfil-baseline/`](bulk-exfil-baseline/) | [`sigma/bulk_exfil_baseline.yml`](../sigma/bulk_exfil_baseline.yml) | `requires_enrichment` | Chain A — UNC5537 replay. The single most replayed Snowflake attack pattern in the wild. |
| [`federated-login-anomaly/`](federated-login-anomaly/) | [`sigma/federated_login_anomaly.yml`](../sigma/federated_login_anomaly.yml) | `requires_correlation` | Chain D — federated-IdP compromise. Captures Golden SAML / Silver SAML class attacks the Snowflake side cannot prevent. |
| [`connector-secret-leak/`](connector-secret-leak/) | [`sigma/connector_secret_leak_in_logs.yml`](../sigma/connector_secret_leak_in_logs.yml) | `production_ready` | CVE-2025-27496 / CVE-2025-46329 class. Includes ingest-time redaction so the SIEM does not become the new long-retention repository for leaked master keys. |

### IdP-side audit ingest templates

| Template directory | Rules it unblocks | What it lands in the SIEM |
|--------------------|-------------------|----------------------------|
| [`idp-okta-system-log/`](idp-okta-system-log/) | federated-login, scim-role-race, oauth-scope-drift (Okta tenants) | Okta System Log + SCIM provisioning + app-grant audit, normalised to the `idp_*` fields the correlation rules expect. |
| [`idp-entra-signin/`](idp-entra-signin/) | federated-login, scim-role-race, oauth-scope-drift (Entra tenants) | Entra SigninLogs + AuditLogs + ProvisioningLogs, normalised to the same `idp_*` field shape. |
| [`oauth-consent-snapshot/`](oauth-consent-snapshot/) | oauth-scope-drift (silent-widening variant) | Daily snapshot of IdP-side OAuth grants for the Snowflake enterprise app, polled from Snowflake via an External Access Integration. Produces the `idp_consent_diff_added` enrichment for Chain L. |

Tenants running both Okta and Entra should deploy both `idp-*` templates
and union the normalised outputs in the federated-login correlator.

Each subdirectory contains:

- `snowflake-side.sql` — the SQL run inside Snowflake that produces the
Expand Down
122 changes: 122 additions & 0 deletions detection/snowflake/enrichment-templates/idp-entra-signin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Entra ID Sign-In Logs — IdP Audit Ingest Template

Drop-in ingest configurations that land Entra ID (Azure AD) sign-in and
directory-audit events in a SIEM in the shape the Snowflake detection
pack expects.

This template is what unblocks the `requires_correlation` rules in the
pack — without it, the rules silently do not fire:

| Rule | What it needs from Entra |
|------|--------------------------|
| [`federated_login_anomaly.yml`](../../sigma/federated_login_anomaly.yml) | `SigninLogs` rows with `ResultType=0`, joined to Snowflake `LOGIN_HISTORY` on `UserPrincipalName`. |
| [`snowflake_scim_role_race.yml`](../../../../tools/cloud-identity/snowflake/detection/sigma/snowflake_scim_role_race.yml) | `AADProvisioningLogs` for the Snowflake enterprise application; correlated to Snowflake-side SCIM PATCH events. |
| [`oauth_integration_scope_drift.yml`](../../../../tools/cloud-identity/snowflake/detection/sigma/oauth_integration_scope_drift.yml) | `AuditLogs` rows for `Consent to application` operations + the daily snapshot maintained by the [`oauth-consent-snapshot/`](../oauth-consent-snapshot/) template. |

## Files

| Path | SIEM | What it does |
|------|------|--------------|
| [`sentinel/entra-diagnostic-settings.json`](sentinel/entra-diagnostic-settings.json) | Sentinel | Diagnostic-settings ARM template that wires Entra logs into the Sentinel Log Analytics workspace. |
| [`sentinel/parser_entra_signin.kql`](sentinel/parser_entra_signin.kql) | Sentinel | KQL function `entra_signin_normalized()` that projects the SIEM-native fields used by the federated-login-anomaly enrichment. |
| [`splunk/azure-ms-cloud-ta.conf`](splunk/azure-ms-cloud-ta.conf) | Splunk | `inputs.conf` + `props.conf` stanzas for the Splunk Add-on for Microsoft Cloud Services (`Splunk_TA_microsoft-cloudservices`). |

## Deployment

### Sentinel

Entra → Sentinel is the cheapest path because both sides are in Azure
and the diagnostic-settings export is first-party.

1. In the Entra ID portal, navigate to **Diagnostic settings** under
*Monitoring*. Deploy the diagnostic setting via the ARM template at
[`sentinel/entra-diagnostic-settings.json`](sentinel/entra-diagnostic-settings.json),
replacing the workspace resource ID with the Sentinel workspace's.
The template wires four log categories:
- `SignInLogs` — interactive user sign-ins (federated-login-anomaly).
- `NonInteractiveUserSignInLogs` — service-principal / app sign-ins
(catches the device-code phish leg of Chain D).
- `AuditLogs` — directory-change audit, including consent grants
(oauth-consent-snapshot).
- `ADFSSignInLogs` — only if the tenant still has AD FS in front of
Snowflake (rare in 2026 but applicable for some on-prem-bridged
deployments).
2. Save [`sentinel/parser_entra_signin.kql`](sentinel/parser_entra_signin.kql)
as a Function with alias `entra_signin_normalized`. The federated-login
enrichment then calls `entra_signin_normalized()` instead of querying
`SigninLogs` directly.
3. Verify ingestion is healthy:
```kql
SigninLogs
| where TimeGenerated > ago(15m)
| summarize count() by ResultType
```
You should see `ResultType=0` rows within 5–15 minutes of an actual
sign-in.

### Splunk

1. Install the Splunk Add-on for Microsoft Cloud Services (version
5.3.0 or later) plus the dependency Microsoft Azure Add-on for Splunk.
The TA owns the OAuth client; the older Event-Hub-fronted ingest is
deprecated for new deployments.
2. Configure the TA's modular input with **Service Principal + Client
Secret** authentication. The service principal needs the Graph API
permissions `AuditLog.Read.All` and `Directory.Read.All`.
3. Drop [`splunk/azure-ms-cloud-ta.conf`](splunk/azure-ms-cloud-ta.conf)
into the TA's local config to pin sourcetypes to `azure:aad:signin`,
`azure:aad:audit`, and `azure:aad:provisioning`.
4. Verify the TA is healthy:
```spl
index=azure sourcetype="azure:aad:signin" earliest=-30m
| stats count by result_type appDisplayName
```

## Field mapping

The downstream rules consume normalised field names; the SIEM-native
column names differ by tool. The parsers produce:

| Normalised field | Source: SigninLogs (Sentinel) | Source: Splunk TA (`azure:aad:signin`) |
|-------------------|--------------------------------|-----------------------------------------|
| `idp_user` | `tolower(UserPrincipalName)` | `lower(user_principal_name)` |
| `idp_event_time` | `TimeGenerated` | `_time` |
| `idp_ip` | `IPAddress` | `ip_address` |
| `idp_app` | `AppDisplayName` | `app_display_name` |
| `idp_outcome` | `ResultType == 0 ? "SUCCESS" : "FAILURE"` | `if(result_type=0, "SUCCESS", "FAILURE")` |
| `idp_auth_method` | `AuthenticationDetails[0].authenticationMethod` | `authentication_details{}.authentication_method` |

The federated-login enrichment template assumes these names. If the SIEM
has existing Entra dashboards using different names, add a translation
layer in the parser rather than renaming across the rules.

## SLA

| Metric | Expected | What to do if missed |
|--------|----------|----------------------|
| End-to-end Entra event → Sentinel searchable | Median 5 min, p95 15 min, occasional spikes to 60 min during vendor incidents | Widen `MAX_INGEST_LAG` in the federated-login enrichment to 45m by default; raise to 60m during Microsoft service incidents (track via Azure Service Health). |
| End-to-end Entra → Splunk via TA | Median 10 min, p95 20 min | Same — widen `dispatch.latest_time` to `-45m@m`. |
| Backfill on outage | Up to 7 days via Graph API pagination | Both the Sentinel diagnostic-settings export and the Splunk TA handle this automatically; verify no permanent gap by spot-checking `_time` vs `_indextime`. |

## `[REQUIRES_TENANT]`

- **Diagnostic-settings target workspace ID**: the ARM template's
`workspaceId` field. Replace before deployment.
- **Service principal client credentials (Splunk path)**: tenant must
mint these. Use a managed identity where the TA supports it.
- **Snowflake enterprise application object ID**: required to scope the
consent-snapshot template; record the value during the IdP-side
Snowflake integration setup.
- **Retention**: 90 days is the typical minimum for the federated-login
retrospective hunt. The Cortex Code pre-1.0.25 exposure window may
dictate a longer retention.

## See also

- [`../federated-login-anomaly/README.md`](../federated-login-anomaly/README.md)
— the correlation rule this template feeds.
- [`../oauth-consent-snapshot/README.md`](../oauth-consent-snapshot/README.md)
— Entra `oauth2PermissionGrants` snapshot pipeline for Chain L.
- [`../idp-okta-system-log/README.md`](../idp-okta-system-log/README.md)
— the Okta equivalent of this template; tenants running both IdPs
should deploy both and union the normalised outputs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"_comment": [
"Entra ID diagnostic-settings export wiring Sign-In + Audit logs",
"into a Sentinel workspace. Deploy with `az deployment tenant create`",
"after substituting the parameters below.",
"",
"The federated_login_anomaly rule needs SignInLogs + AuditLogs at",
"minimum. ProvisioningLogs unblocks snowflake_scim_role_race. The",
"remaining categories are included because they are inexpensive and",
"useful for Cortex Code retrospective hunting."
],
"parameters": {
"diagnosticSettingName": {
"type": "string",
"defaultValue": "entra-to-sentinel-snowflake-pack",
"metadata": {
"description": "Diagnostic setting display name."
}
},
"sentinelWorkspaceResourceId": {
"type": "string",
"metadata": {
"description": "Resource ID of the Sentinel Log Analytics workspace. Form: /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<workspace>."
}
},
"retentionDays": {
"type": "int",
"defaultValue": 90,
"minValue": 90,
"metadata": {
"description": "Workspace-side retention floor for these logs. 90 days is the federated-login retrospective-hunt minimum; raise if the Cortex Code pre-1.0.25 exposure window in your tenant is longer."
}
}
},
"resources": [
{
"type": "Microsoft.aadiam/diagnosticSettings",
"apiVersion": "2017-04-01",
"name": "[parameters('diagnosticSettingName')]",
"properties": {
"workspaceId": "[parameters('sentinelWorkspaceResourceId')]",
"logs": [
{
"category": "SignInLogs",
"enabled": true,
"_comment": "Interactive user sign-ins. The primary federated-login-anomaly source."
},
{
"category": "NonInteractiveUserSignInLogs",
"enabled": true,
"_comment": "Background / refresh-token sign-ins. Catches the device-code phish leg of Chain D — UserAuthenticationMethod will show 'deviceCode' on the malicious side."
},
{
"category": "ServicePrincipalSignInLogs",
"enabled": true,
"_comment": "Service-principal sign-ins. Catches the Chain L OAuth scope-drift leg from the application-permission path."
},
{
"category": "ManagedIdentitySignInLogs",
"enabled": false,
"_comment": "Disabled by default — high volume and unrelated to the Snowflake detection pack. Enable if the tenant uses managed identities to sign into Snowflake."
},
{
"category": "AuditLogs",
"enabled": true,
"_comment": "Directory-change audit, including 'Consent to application'. Unblocks oauth-consent-snapshot retrospective queries."
},
{
"category": "ProvisioningLogs",
"enabled": true,
"_comment": "Entra → Snowflake SCIM provisioning. Required for snowflake_scim_role_race correlation."
},
{
"category": "RiskyUsers",
"enabled": true
},
{
"category": "UserRiskEvents",
"enabled": true
},
{
"category": "ADFSSignInLogs",
"enabled": false,
"_comment": "Enable only if the tenant has AD FS in front of Snowflake. Rare in 2026."
}
]
}
}
],
"outputs": {
"verificationQuery": {
"type": "string",
"value": "SigninLogs | where TimeGenerated > ago(15m) | summarize count() by ResultType -- should return rows within 10–15m of a real sign-in."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Sentinel KQL parser for Entra ID Sign-In events, normalised to the
// field names the federated-login-anomaly enrichment expects.
//
// Save as Function with alias: entra_signin_normalized
//
// The federated-login-anomaly enrichment can union AAD SigninLogs and
// okta_signin_normalized() to support tenants running both IdPs:
//
// union (entra_signin_normalized()), (okta_signin_normalized())

let snowflake_app_pattern = @"(?i)snowflake"; // matches application names like "Snowflake", "Snowflake Production", etc.
SigninLogs
| where TimeGenerated > ago(2h)
| where ResultType == 0
| where AppDisplayName matches regex snowflake_app_pattern
or ResourceDisplayName matches regex snowflake_app_pattern
| extend auth_methods = parse_json(AuthenticationDetails)
| extend idp_auth_method = tostring(auth_methods[0].authenticationMethod)
| extend idp_user = tolower(UserPrincipalName)
| project
idp_event_time = TimeGenerated,
idp_user,
idp_ip = IPAddress,
idp_app = AppDisplayName,
idp_outcome = "SUCCESS",
idp_auth_method,
raw_event_type = "SigninLogs"
Loading
Loading