Detection rules › Kusto
Cross-Cloud Suspicious Compute resource creation in GCP
'This detection identifies potential suspicious activity across multi-cloud environments by combining AWS GuardDuty findings with GCP Audit Logs. It focuses on AWS activities related to unauthorized access, credential abuse, and unusual behaviors, as well as GCP instances creation with non-Google service account users. The query aims to provide a comprehensive view of cross-cloud security incidents for proactive threat detection and response.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078 Valid Accounts, T1566 Phishing |
| Execution | T1059 Command and Scripting Interpreter |
| Persistence | T1078 Valid Accounts, T1547 Boot or Logon Autostart Execution |
| Privilege Escalation | T1078 Valid Accounts, T1547 Boot or Logon Autostart Execution, T1548 Abuse Elevation Control Mechanism |
| Credential Access | T1552 Unsecured Credentials |
| Discovery | T1069 Permission Groups Discovery |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
Rule body kusto
id: 5c847e47-0a07-4c01-ab99-5817ad6cb11e
name: Cross-Cloud Suspicious Compute resource creation in GCP
description: |
'This detection identifies potential suspicious activity across multi-cloud environments by combining AWS GuardDuty findings with GCP Audit Logs. It focuses on AWS activities related to unauthorized access, credential abuse, and unusual behaviors, as well as GCP instances creation with non-Google service account users. The query aims to provide a comprehensive view of cross-cloud security incidents for proactive threat detection and response.'
severity: Low
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
- connectorId: AWSS3
dataTypes:
- AWSGuardDuty
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Persistence
- PrivilegeEscalation
- CredentialAccess
- Discovery
- LateralMovement
relevantTechniques:
- T1566
- T1059
- T1078
- T1547
- T1548
- T1069
- T1552
query: |
// Materialize AWS GuardDuty findings
let AwsAlert = materialize (
AWSGuardDuty
// Filter for specific activity types in AWS GuardDuty
| where ActivityType has_any (
"Backdoor:EC2/DenialOfService.UnusualProtocol",
"CredentialAccess:Kubernetes/MaliciousIPCaller",
"CredentialAccess:Kubernetes/SuccessfulAnonymousAccess",
"CredentialAccess:Kubernetes/TorIPCaller",
"CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
"CredentialAccess:RDS/TorIPCaller.FailedLogin",
"CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
"Discovery:Kubernetes/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:EC2/TorClient",
"UnauthorizedAccess:IAMUser/TorIPCaller",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B"
)
// Extract and transform AWS GuardDuty attributes
| extend
AWSAlertId = Id,
AWSAlertTitle = Title,
AWSAlertDescription = Description,
AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.awsApiCallAction.remoteIpDetails.ipAddressV4),
AWSAlertUserNameEntity = tostring(parse_json(ResourceDetails).accessKeyDetails.userName),
InstanceType = tostring(parse_json(ResourceDetails).instanceDetails.instanceType),
AWSTargetingService = parse_json(ServiceDetails).additionalInfo.apiCalls,
AWSAlertTime = TimeCreated,
AWSAlertLink= tostring(strcat('https://us-east-1.console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?macros=current&fId=', Id)),
Severity =
case (
Severity >= 7.0,
"High",
Severity between (4.0 .. 6.9),
"Medium",
Severity between (1.0 .. 3.9),
"Low",
"Unknown"
)
// Extract API call details and count
| mv-apply AIPCall = AWSTargetingService on
(
where AIPCall has "name"
| extend APICallName = tostring(AIPCall.name), APICallCount = tostring(AIPCall["count"])
)
// Select distinct attributes for further analysis
| distinct
AWSAlertTime,
ActivityType,
Severity,
AWSAlertId,
AWSAlertTitle,
AWSAlertDescription,
AWSAlertLink,
Arn,
AWSresourceType,
AWSNetworkEntity,
AWSAlertUserNameEntity,
InstanceType,
APICallName,
APICallCount
);
// Materialize GCP Audit Logs related to VM instance creation
let GCPVMActivity= materialize(
GCPAuditLogs
// Filter for Compute Engine instances insertions
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
// Extract and transform relevant GCP Audit Log attributes
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMDetails= parse_json(AuthorizationInfo),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMDescription= tostring(parse_json(Request).description),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1]),
Tags= tostring(parse_json(Request).tags),
RequestJS = parse_json(Request)
// Filter out service account-related activities and private IP addresses
| where GCPUserUPN !has "gserviceaccount.com"
| extend Name = tostring(split(GCPUserUPN, "@")[0]), UPNSuffix = tostring(split(GCPUserUPN, "@")[1])
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
// Select relevant attributes for further analysis
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType,
Name,
UPNSuffix
);
// Join AWS and GCP activities based on matching IP addresses
AwsAlert
| join kind= inner (GCPVMActivity)
on
$left.AWSNetworkEntity == $right.GCPUserIp
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: GCPUserIp
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: GCPUserUPN
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
customDetails:
AWSAlertUserName: AWSAlertUserNameEntity
AWSArn: Arn
AWSresourceType: AWSresourceType
AWSInstanceType: InstanceType
AWSAPICallName: APICallName
AWSAPICallCount: APICallCount
GCPUserAgent: GCPUserUA
GCPVMName: VMName
GCPProjectId: ProjectId
GCPVMType: VMType
CorrelationWith: "GCPAuditLogs"
alertDetailsOverride:
alertDisplayNameFormat: "{{AWSNetworkEntity}} from {{AWSAlertTitle}} observed in GCP compute activity with {{GCPUserUPN}}"
alertDescriptionFormat: " This detection compiles and correlates unauthorized user access alerts originating from AWS GuardDuty With Alert Description '{{AWSAlertDescription}}' assocated with GCP compute activities. It focuses on AWS GuardDuty alerts related to unauthorized user access, specifically targeting network IP associations tied to activities such as logins from malicious IP addresses or instance credential exfiltration attempts. The detection leverages these common network IP advisories to detect and pinpoint unauthorized users attempting to access both AWS and Azure resources. \n\n AWS ALert Link : '{{AWSAlertLink}}' \n\n Find More Details :https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html"
alertSeverityColumnName: Severity
alertDynamicProperties:
- alertProperty: AlertLink
value: AWSAlertLink
- alertProperty: ProviderName
value: "AWS"
- alertProperty: ProductComponentName
value: "AWSGuarduty"
kind: Scheduled
version: 1.0.2
Stages and Predicates
Let binding: GCPVMActivity
let GCPVMActivity = materialize(
GCPAuditLogs
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMDetails= parse_json(AuthorizationInfo),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMDescription= tostring(parse_json(Request).description),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1]),
Tags= tostring(parse_json(Request).tags),
RequestJS = parse_json(Request)
| where GCPUserUPN !has "gserviceaccount.com"
| extend Name = tostring(split(GCPUserUPN, "@")[0]), UPNSuffix = tostring(split(GCPUserUPN, "@")[1])
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType,
Name,
UPNSuffix
);
The stages below define let AwsAlert (the rule's main pipeline source).
Stage 1: source
AWSGuardDuty
Stage 2: where
| where ActivityType has_any (
"Backdoor:EC2/DenialOfService.UnusualProtocol",
"CredentialAccess:Kubernetes/MaliciousIPCaller",
"CredentialAccess:Kubernetes/SuccessfulAnonymousAccess",
"CredentialAccess:Kubernetes/TorIPCaller",
"CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
"CredentialAccess:RDS/TorIPCaller.FailedLogin",
"CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
"Discovery:Kubernetes/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:EC2/TorClient",
"UnauthorizedAccess:IAMUser/TorIPCaller",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B"
)
Stage 3: extend
| extend
AWSAlertId = Id,
AWSAlertTitle = Title,
AWSAlertDescription = Description,
AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.awsApiCallAction.remoteIpDetails.ipAddressV4),
AWSAlertUserNameEntity = tostring(parse_json(ResourceDetails).accessKeyDetails.userName),
InstanceType = tostring(parse_json(ResourceDetails).instanceDetails.instanceType),
AWSTargetingService = parse_json(ServiceDetails).additionalInfo.apiCalls,
AWSAlertTime = TimeCreated,
AWSAlertLink= tostring(strcat('https://us-east-1.console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?macros=current&fId=', Id)),
Severity =
case (
Severity >= 7.0,
"High",
Severity between (4.0 .. 6.9),
"Medium",
Severity between (1.0 .. 3.9),
"Low",
"Unknown"
)
Severity =Severity >= 7.0"High"Severity >= 4.0 and Severity <= 6.9"Medium"Severity >= 1.0 and Severity <= 3.9"Low""Unknown"Stage 4: kusto:mv-apply
| mv-apply AIPCall = AWSTargetingService on
(
where AIPCall has "name"
| extend APICallName = tostring(AIPCall.name), APICallCount = tostring(AIPCall["count"])
)
Stage 5: distinct
| distinct
AWSAlertTime,
ActivityType,
Severity,
AWSAlertId,
AWSAlertTitle,
AWSAlertDescription,
AWSAlertLink,
Arn,
AWSresourceType,
AWSNetworkEntity,
AWSAlertUserNameEntity,
InstanceType,
APICallName,
APICallCount
The stages below run on AwsAlert (the outer pipeline).
Stage 6: join
AwsAlert
| join kind= inner (GCPVMActivity)
on
$left.AWSNetworkEntity == $right.GCPUserIp
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
GCPUserUPN | match | gserviceaccount.com |
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 |
|---|---|---|
AIPCall | match |
|
ActivityType | match |
|
GCPUserIp | is_not_null | |
GCPUserIp | ne |
|
MethodName | ends_with |
|
ServiceName | eq |
|
VMOperation | eq |
|
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 |
|---|---|
AWSAlertDescription | extend |
AWSAlertId | extend |
AWSAlertLink | extend |
AWSAlertTime | extend |
AWSAlertTitle | extend |
AWSAlertUserNameEntity | extend |
AWSNetworkEntity | extend |
AWSTargetingService | extend |
AWSresourceType | extend |
InstanceType | extend |
Severity | extend |