Detection rules › Kusto
AWSCloudTrail - Suspicious AWS CLI Command Execution
'This detection focuses on identifying potentially suspicious activities involving the execution of AWS Command Line Interface (CLI) commands, particularly focusing on reconnaissance operations.'
MITRE ATT&CK coverage
Rule body kusto
id: 8c2dc344-9352-4ca1-8863-b1b7a5e09e59
name: AWSCloudTrail - Suspicious AWS CLI Command Execution
description: |
'This detection focuses on identifying potentially suspicious activities involving the execution of AWS Command Line Interface (CLI) commands, particularly focusing on reconnaissance operations.'
severity: Medium
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Reconnaissance
relevantTechniques:
- T1595
- T1592.004
- T1589.002
- T1589.003
- T1590
- T1591
- T1596
query: |
let SuspiciousCommands= pack_array('iam.list-users', 'iam.list-groups', 'ec2.describe-vpcs', 'ec2.describe-subnets', 'route53.list-hosted-zones', 'kms.list-keys', 'kms.list-aliases', 'ecs.list-clusters', 'ecs.list-services', 'iam.list-roles', 'iam.get-user''iam.list-access-keys', 'ec2.describe-security-groups', 'ec2.describe-network-acls', 'ec2.describe-network-interfaces', 'ec2.describe-route-tables', 'ec2.describe-internet-gateways', 'ec2.describe-vpc-peering-connections', 'ec2.describe-network-interfaces', 'ec2.describe-network-interfaces', 'ec2.describe-transit-gateway-vpc-attachment', 'ec2.describe-vpc');
// Retrieve AWS CloudTrail events
AWSCloudTrail
// Filter events with UserAgent starting with "aws-cli"
| where UserAgent startswith "aws-cli"
// Extract the command from the UserAgent using string splitting
| extend command = tostring(split(UserAgent, "off command/", 1)[0])
// Filter events based on predefined suspicious command list
| where command has_any (SuspiciousCommands)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
// Summarize relevant information for further analysis
| summarize
CommadCount = dcount(command),
EventCount = dcount(EventName),
commands = make_list(command),
Events = make_list(EventName)
by
bin(TimeGenerated, 1min),
RecipientAccountId, AccountName, AccountUPNSuffix,
UserIdentityUserName,
SourceIpAddress,
SessionMfaAuthenticated
// Filter out results with a sufficient count of unique suspicious commands in 1 min time window
| where CommadCount >= 8
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
customDetails:
SuspiciousCommand: commands
AWSUser: UserIdentityUserName
AWSUserIp: SourceIpAddress
alertDetailsOverride:
alertDisplayNameFormat: Suspicious AWS CLI reconnaissance by {{AccountName}} - {{CommadCount}} commands
alertDescriptionFormat: User {{AccountName}} executed {{CommadCount}} suspicious AWS CLI reconnaissance commands from {{SourceIpAddress}} within 1 minute window.
kind: Scheduled
version: 1.0.2
Stages and Predicates
Let binding: SuspiciousCommands
let SuspiciousCommands = pack_array('iam.list-users', 'iam.list-groups', 'ec2.describe-vpcs', 'ec2.describe-subnets', 'route53.list-hosted-zones', 'kms.list-keys', 'kms.list-aliases', 'ecs.list-clusters', 'ecs.list-services', 'iam.list-roles', 'iam.get-user''iam.list-access-keys', 'ec2.describe-security-groups', 'ec2.describe-network-acls', 'ec2.describe-network-interfaces', 'ec2.describe-route-tables', 'ec2.describe-internet-gateways', 'ec2.describe-vpc-peering-connections', 'ec2.describe-network-interfaces', 'ec2.describe-network-interfaces', 'ec2.describe-transit-gateway-vpc-attachment', 'ec2.describe-vpc');
Stage 1: source
AWSCloudTrail
Stage 2: where
| where UserAgent startswith "aws-cli"
Stage 3: extend
| extend command = tostring(split(UserAgent, "off command/", 1)[0])
Stage 4: where
| where command has_any (SuspiciousCommands)
References SuspiciousCommands (defined above).
Stage 5: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 6: summarize
| summarize
CommadCount = dcount(command),
EventCount = dcount(EventName),
commands = make_list(command),
Events = make_list(EventName)
by
bin(TimeGenerated, 1min),
RecipientAccountId, AccountName, AccountUPNSuffix,
UserIdentityUserName,
SourceIpAddress,
SessionMfaAuthenticated
Stage 7: where
| where CommadCount >= 8
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
CommadCount | ge |
|
UserAgent | starts_with |
|
command | match |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AccountName | summarize |
AccountUPNSuffix | summarize |
CommadCount | summarize |
EventCount | summarize |
Events | summarize |
RecipientAccountId | summarize |
SessionMfaAuthenticated | summarize |
SourceIpAddress | summarize |
UserIdentityUserName | summarize |
commands | summarize |