Detection rules › Kusto

Cross-Cloud Unauthorized Credential Access Detection From AWS RDS Login

Severity
medium
Time window
1d
Group by
AWSNetworkEntity, AuthenticationRequirement, ClientAppUsed, ConditionalAccessStatus, IPAddress, OperationName, RiskDetail, UserAgent, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

'This detection correlates AWS GuardDuty Credential Access alerts related to Amazon Relational Database Service (RDS) activity with Azure portal sign-in activities. It identifies successful and failed logins, anomalous behavior, and malicious IP access. By joining these datasets on network entities and IP addresses, it detects unauthorized credential access attempts across AWS and Azure resources, enhancing cross-cloud security monitoring.'

MITRE ATT&CK coverage

Rule body kusto

id: 122fbc6a-57ab-4aa7-b9a9-51ac4970cac1
name: Cross-Cloud Unauthorized Credential Access Detection From AWS RDS Login 
description: |
  'This detection correlates AWS GuardDuty Credential Access alerts related to Amazon Relational Database Service (RDS) activity with Azure portal sign-in activities. It identifies successful and failed logins, anomalous behavior, and malicious IP access. By joining these datasets on network entities and IP addresses, it detects unauthorized credential access attempts across AWS and Azure resources, enhancing cross-cloud security monitoring.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AWSS3
    dataTypes:
      - AWSGuardDuty
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - InitialAccess
relevantTechniques:
  - T1557
  - T1110
  - T1110.003
  - T1110.004
  - T1606
  - T1556
  - T1133
query: |
    // Define variable 'AwsAlert' to collect AWS GuardDuty CredentialAccess alerts related to Amazon Relational Database Service (RDS) activity
    let AwsAlert = materialize (
        AWSGuardDuty
        | where ActivityType has_any (
            "CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
            "CredentialAccess:RDS/TorIPCaller.FailedLogin",
            "CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
            "CredentialAccess:RDS/AnomalousBehavior.SuccessfulLogin",
            "CredentialAccess:RDS/MaliciousIPCaller.SuccessfulLogin",
            "CredentialAccess:RDS/MaliciousIPCaller.FailedLogin"
            )
        | extend
            AWSAlertId = Id, 
            AWSAlertTitle = Title,
            AWSAlertDescription = Description,
            AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
            AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.rdsLoginAttemptAction.remoteIpDetails.ipAddressV4),
            RDSInstanceId = tostring(parse_json(ResourceDetails).rdsDbInstanceDetails.dbInstanceIdentifier),
            RDSUser = tostring(parse_json(ResourceDetails).rdsDbUserDetails.user),
            RDSApplication = tostring(parse_json(ResourceDetails).rdsDbUserDetails.application),
            RDSactionType = tostring(parse_json(ServiceDetails).action.actionType),
            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")
        | distinct
            AWSAlertTime,
            ActivityType,
            AWSAlertId,
            AWSAlertLink,
            AWSAlertTitle,
            AWSAlertDescription,
            AWSresourceType,
            Arn,
            Severity,
            RDSactionType,
            RDSApplication,
            RDSInstanceId,
            RDSUser,
            AWSNetworkEntity
        );
      // Define variable 'Azure_sigin' to collect Azure portal sign-in activities
      let Azure_sigin = materialize (
          SigninLogs
          | where AppDisplayName == "Azure Portal"
          | where isnotempty(OriginalRequestId)
          | summarize 
              AzureSuccessfulEvent = countif(ResultType == 0), 
              AzureFailedEvent = countif(ResultType != 0), 
              totalAzureLoginEventId = dcount(OriginalRequestId), 
              AzureFailedEventsCount = dcountif(OriginalRequestId, ResultType != 0), 
              AzureSuccessfuleventsCount = dcountif(OriginalRequestId, ResultType == 0),
              AzureSetOfFailedevents = makeset(iff(ResultType != 0, OriginalRequestId, ""), 5), 
              AzureSetOfSuccessfulEvents = makeset(iff(ResultType == 0, OriginalRequestId, ""), 5) 
              by 
              IPAddress, 
              UserPrincipalName, 
              bin(TimeGenerated, 1min), 
              UserAgent,
              ConditionalAccessStatus,
              OperationName,
              RiskDetail,
              AuthenticationRequirement,
              ClientAppUsed
          // Extracting the name and UPN suffix from UserPrincipalName
          | extend
              Name = tostring(split(UserPrincipalName, '@')[0]),
              UPNSuffix = tostring(split(UserPrincipalName, '@')[1])
          );
      // Join 'AwsAlert' and 'Azure_sigin' on the AWS Network Entity and Azure IP Address
      AwsAlert
      | join kind=inner Azure_sigin on $left.AWSNetworkEntity == $right.IPAddress
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
customDetails:
  AWSAlertUserName: RDSUser
  AWSArn: Arn
  AWSresourceType: AWSresourceType
  AWSInstanceType: RDSactionType
  AWSAplicationName: RDSApplication
  AWSInstanceId: RDSInstanceId
  AzureUserAgent: UserAgent
  AzureUser: UserPrincipalName
  AzureClientAppUsed: ClientAppUsed
  AzConditionalAccess: ConditionalAccessStatus
  AzureOperationName: OperationName
  AzureRiskDetail: RiskDetail
  AzAuthRequirement: AuthenticationRequirement
  alertSeverity: Severity
alertDetailsOverride:
  alertDisplayNameFormat: "IP address {{IPAddress}} in {{AWSAlertTitle}} seen in Azure Signin Logs with {{UserPrincipalName}}"
  alertDescriptionFormat: "This detection correlates AWS GuardDuty Credential Access alert described '{{AWSAlertDescription}}' related to Amazon Relational Database Service (RDS) activity with Azure portal sign-in activities. It identifies successful and failed logins, anomalous behavior, and malicious IP access. By joining these datasets on network entities and IP addresses, it detects unauthorized credential access attempts across AWS and Azure resources, enhancing cross-cloud security monitoring. \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: ProductName
      value: "AWSGuardDuty"
    - alertProperty: ProductComponentName
      value: "AWSGuardDuty"
kind: Scheduled
version: 1.0.3

Stages and Predicates

Let binding: Azure_sigin

let Azure_sigin = materialize (
      SigninLogs
      | where AppDisplayName == "Azure Portal"
      | where isnotempty(OriginalRequestId)
      | summarize 
          AzureSuccessfulEvent = countif(ResultType == 0), 
          AzureFailedEvent = countif(ResultType != 0), 
          totalAzureLoginEventId = dcount(OriginalRequestId), 
          AzureFailedEventsCount = dcountif(OriginalRequestId, ResultType != 0), 
          AzureSuccessfuleventsCount = dcountif(OriginalRequestId, ResultType == 0),
          AzureSetOfFailedevents = makeset(iff(ResultType != 0, OriginalRequestId, ""), 5), 
          AzureSetOfSuccessfulEvents = makeset(iff(ResultType == 0, OriginalRequestId, ""), 5) 
          by 
          IPAddress, 
          UserPrincipalName, 
          bin(TimeGenerated, 1min), 
          UserAgent,
          ConditionalAccessStatus,
          OperationName,
          RiskDetail,
          AuthenticationRequirement,
          ClientAppUsed
      | extend
          Name = tostring(split(UserPrincipalName, '@')[0]),
          UPNSuffix = tostring(split(UserPrincipalName, '@')[1])
      );

The stages below define let AwsAlert (the rule's main pipeline source).

Stage 1: source

AWSGuardDuty

Stage 2: where

| where ActivityType has_any (
        "CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
        "CredentialAccess:RDS/TorIPCaller.FailedLogin",
        "CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
        "CredentialAccess:RDS/AnomalousBehavior.SuccessfulLogin",
        "CredentialAccess:RDS/MaliciousIPCaller.SuccessfulLogin",
        "CredentialAccess:RDS/MaliciousIPCaller.FailedLogin"
        )

Stage 3: extend

| extend
        AWSAlertId = Id, 
        AWSAlertTitle = Title,
        AWSAlertDescription = Description,
        AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
        AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.rdsLoginAttemptAction.remoteIpDetails.ipAddressV4),
        RDSInstanceId = tostring(parse_json(ResourceDetails).rdsDbInstanceDetails.dbInstanceIdentifier),
        RDSUser = tostring(parse_json(ResourceDetails).rdsDbUserDetails.user),
        RDSApplication = tostring(parse_json(ResourceDetails).rdsDbUserDetails.application),
        RDSactionType = tostring(parse_json(ServiceDetails).action.actionType),
        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: distinct

| distinct
        AWSAlertTime,
        ActivityType,
        AWSAlertId,
        AWSAlertLink,
        AWSAlertTitle,
        AWSAlertDescription,
        AWSresourceType,
        Arn,
        Severity,
        RDSactionType,
        RDSApplication,
        RDSInstanceId,
        RDSUser,
        AWSNetworkEntity

The stages below run on AwsAlert (the outer pipeline).

Stage 5: join

AwsAlert
| join kind=inner Azure_sigin on $left.AWSNetworkEntity == $right.IPAddress

Stage 6: summarize

summarize by IPAddress, UserPrincipalName, UserAgent, ConditionalAccessStatus, OperationName, RiskDetail, AuthenticationRequirement, ClientAppUsed

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.

FieldKindValues
ActivityTypematch
  • CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce
  • CredentialAccess:RDS/AnomalousBehavior.SuccessfulLogin
  • CredentialAccess:RDS/MaliciousIPCaller.FailedLogin
  • CredentialAccess:RDS/MaliciousIPCaller.SuccessfulLogin
  • CredentialAccess:RDS/TorIPCaller.FailedLogin
  • CredentialAccess:RDS/TorIPCaller.SuccessfulLogin
AppDisplayNameeq
  • Azure Portal transforms: cased
OriginalRequestIdis_not_null
  • (no value, null check)

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.

FieldSource
AuthenticationRequirementsummarize
ClientAppUsedsummarize
ConditionalAccessStatussummarize
IPAddresssummarize
OperationNamesummarize
RiskDetailsummarize
UserAgentsummarize
UserPrincipalNamesummarize