Detection rules › Kusto

Cross-Cloud Suspicious Compute resource creation in GCP

Severity
low
Time window
1d
Group by
AWSNetworkEntity, GCPUserIp
Source
github.com/Azure/Azure-Sentinel

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

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

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 AwsAlert is inlined into the numbered stages below.

Let binding: GCPVMActivity used in Stage 6

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
    );

Stages 1 to 5 define let AwsAlert (the rule's main pipeline source); stage 6 runs on it.

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 =
ifSeverity >= 7.0"High"
elifSeverity >= 4.0 and Severity <= 6.9"Medium"
elifSeverity >= 1.0 and Severity <= 3.9"Low"
else"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

Stage 6: join

AwsAlert
| join kind= inner (GCPVMActivity)
    on
    $left.AWSNetworkEntity == $right.GCPUserIp

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
GCPUserUPNmatchgserviceaccount.comexcludes:GCPUserUPN field:"GCPUserUPN" value:"gserviceaccount.com"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AIPCallmatch
  • name transforms: term
field:"AIPCall" kind:match value:"name"
ActivityTypematch
  • Backdoor:EC2/DenialOfService.UnusualProtocol transforms: term
  • CredentialAccess:Kubernetes/MaliciousIPCaller transforms: term
  • CredentialAccess:Kubernetes/SuccessfulAnonymousAccess transforms: term
  • CredentialAccess:Kubernetes/TorIPCaller transforms: term
  • CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce transforms: term
  • CredentialAccess:RDS/TorIPCaller.FailedLogin transforms: term
  • CredentialAccess:RDS/TorIPCaller.SuccessfulLogin transforms: term
  • Discovery:Kubernetes/MaliciousIPCaller transforms: term
  • Recon:IAMUser/MaliciousIPCaller.Custom transforms: term
  • UnauthorizedAccess:EC2/TorClient transforms: term
  • UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B transforms: term
  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS transforms: term
  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS transforms: term
  • UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom transforms: term
  • UnauthorizedAccess:IAMUser/TorIPCaller transforms: term
field:"ActivityType" kind:match
GCPUserIpis_not_null
  • (no value, null check)
field:"GCPUserIp" kind:is_not_null
GCPUserIpne
  • private
field:"GCPUserIp" kind:ne value:"private"
MethodNameends_with
  • instances.insert
field:"gcp::method_name" kind:ends_with value:"instances.insert"
ServiceNameeq
  • compute.googleapis.com
field:"ServiceName" kind:eq value:"compute.googleapis.com"
VMOperationeq
  • insert
field:"VMOperation" kind:eq value:"insert"

Output fields

These fields are emitted when the rule matches.

FieldSource
AWSAlertDescriptionextend
AWSAlertIdextend
AWSAlertLinkextend
AWSAlertTimeextend
AWSAlertTitleextend
AWSAlertUserNameEntityextend
AWSNetworkEntityextend
AWSTargetingServiceextend
AWSresourceTypeextend
InstanceTypeextend
Severityextend