aws-cloudwatch-tamper-metric-filter
logs:DeleteMetricFilter or logs:PutMetricFilter on a log group removes or corrupts the metric filter expression that feeds CloudWatch alarms, creating a detection blind spot.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?log_group) ==
LoggingService
?log_group.provider_type == 'AWS::Logs::LogGroup'
?log_group.metricFilterCount > 0
?principal has EFFECTIVE logs:DeleteMetricFilter OR logs:PutMetricFilter on ?log_group ARN
emit
| source type | Identity |
|---|---|
| target type | LoggingService |
| source | ?principal |
| target | ?log_group |
| permissions | logs:DeleteMetricFilter logs:PutMetricFilter |
| conditions | iam_permission scp_or_org_policy |
| state logic | ACTIVE when logs:DeleteMetricFilter is confirmed EFFECTIVE on the log group ARN (identity-based policy + no explicit deny + no SCP deny) — definitive alarm silence (filter gone, metric no longer increments). CONDITIONAL(condition_expression) when only logs:PutMetricFilter is confirmed effective — alarm silence requires the attacker to correctly rewrite the filter expression to no longer match the events they generate. BLOCKED by an explicit deny on logs:DeleteMetricFilter or logs:PutMetricFilter. This edge is defense evasion / cover-tracks only — NOT escalation. |
Narrative
{principal.name} can delete or overwrite a metric filter on {log_group.name} (logs:DeleteMetricFilter / logs:PutMetricFilter). The affected metric filter feeds a CloudWatch alarm; removing or corrupting the filter prevents the metric from incrementing, causing the alarm to remain silent even when the original condition occurs. This creates a detection blind spot - it does not grant access to any resource.
Raw rule rules/derived/aws/cloudwatch.yaml
id: aws-cloudwatch-tamper-metric-filter
emits: CanModifyConfiguration
description: logs:DeleteMetricFilter or logs:PutMetricFilter on a log group removes or corrupts the metric
filter expression that feeds CloudWatch alarms, creating a detection blind spot.
match:
- - principal: null
- HasPermission
- log_group: null
where:
- node_type(?log_group) == LoggingService
- ?log_group.provider_type == 'AWS::Logs::LogGroup'
- ?log_group.metricFilterCount > 0
- ?principal has EFFECTIVE logs:DeleteMetricFilter OR logs:PutMetricFilter on ?log_group ARN
emit:
source_type: Identity
target_type: LoggingService
source: ?principal
target: ?log_group
permissions:
- logs:DeleteMetricFilter
- logs:PutMetricFilter
conditions:
- iam_permission
- scp_or_org_policy
state_logic: "ACTIVE when logs:DeleteMetricFilter is confirmed EFFECTIVE on the log group ARN (identity-based\
\ policy + no explicit deny + no SCP deny) \u2014 definitive alarm silence (filter gone, metric no\
\ longer increments). CONDITIONAL(condition_expression) when only logs:PutMetricFilter is confirmed\
\ effective \u2014 alarm silence requires the attacker to correctly rewrite the filter expression\
\ to no longer match the events they generate. BLOCKED by an explicit deny on logs:DeleteMetricFilter\
\ or logs:PutMetricFilter. This edge is defense evasion / cover-tracks only \u2014 NOT escalation."
confidence: min(contributing_confidences) * 0.9
derived_from:
- ?principal HasPermission ?log_group (logs:DeleteMetricFilter / logs:PutMetricFilter effective permission)
false_positive_note: "Two distinct actions with different state values: logs:DeleteMetricFilter (remove\
\ filter entirely \u2192 definitive silence, ACTIVE) vs. logs:PutMetricFilter (overwrite expression\
\ \u2192 conditional on the attacker correctly rewriting the filter to exclude their activity, emit\
\ CONDITIONAL(condition_expression) if rewrite precision is unknown). A log group with NO metric filters\
\ has no alarm surface to suppress (metricFilterCount check prevents zero-value emissions). This does\
\ NOT delete the log group or its events; the historical log events remain and may be queried later.\
\ This does NOT chain to CanExecuteAs or CanEnterAccount. Narrative MUST say \"creates a detection\
\ blind spot\" not \"grants access.\" Consider splitting this rule into two (aws-cloudwatch- delete-metric-filter\
\ \u2192 ACTIVE and aws-cloudwatch-overwrite-metric-filter \u2192 CONDITIONAL) if explicit state separation\
\ is needed for downstream consumers."
narrative: "{principal.name} can delete or overwrite a metric filter on {log_group.name} (logs:DeleteMetricFilter\
\ / logs:PutMetricFilter). The affected metric filter feeds a CloudWatch alarm; removing or corrupting\
\ the filter prevents the metric from incrementing, causing the alarm to remain silent even when the\
\ original condition occurs. This creates a detection blind spot \u2014 it does not grant access to\
\ any resource."