Detection rules › Kusto
Cross-Cloud Unauthorized Credential Access Detection From AWS RDS Login
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
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Sign-in naming a target application |
Rule body
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 AwsAlert is inlined into the numbered stages below.
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])
);
Stages 1 to 4 define let AwsAlert (the rule's main pipeline source); stages 5 to 6 run on it.
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 =if
Severity >= 7.0"High"elif
Severity >= 4.0 and Severity <= 6.9"Medium"elif
Severity >= 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
Stage 5: join
AwsAlert
| join kind=inner Azure_sigin on $left.AWSNetworkEntity == $right.IPAddress
Stage 6: summarize aggregation inside the join branch
summarize by IPAddress, UserPrincipalName, UserAgent, ConditionalAccessStatus, OperationName, RiskDetail, AuthenticationRequirement, ClientAppUsed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | match |
| field:"ActivityType" kind:match |
AppDisplayName | eq |
| field:"AppDisplayName" kind:eq value:"Azure Portal" |
OriginalRequestId | is_not_null | field:"OriginalRequestId" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AuthenticationRequirement | summarize |
ClientAppUsed | summarize |
ConditionalAccessStatus | summarize |
IPAddress | summarize |
OperationName | summarize |
RiskDetail | summarize |
UserAgent | summarize |
UserPrincipalName | summarize |