Query Details

Audit Logs Disabled Account Signins Across Many Applications

Query

let query_frequency = 1h;
let query_period = 14d;
let ipv4_prefix_mask = 23;
let _ExpectedIPAddresses = toscalar(
    union _GetWatchlist("IP-CorporateCollaborators"), _GetWatchlist("IP-Vendors")
    | summarize make_list(IPAddress)
);
let _ExpectedLocations = toscalar(
    _GetWatchlist("Activity-ExpectedSignificantActivity")
    | where Activity == "CorporateGeolocation"
    | summarize make_list(Auxiliar)
);
let _ExpectedASNs = toscalar(
    _GetWatchlist("Activity-ExpectedSignificantActivity")
    | where Activity == "CommonUserASN"
    | summarize make_list(Auxiliar)
);
let _DisabledAttempts =
    union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(query_frequency)
    | where ResultType == "50057"
    | summarize
        TimeGenerated = arg_min(TimeGenerated, OriginalRequestId),
        AppDisplayName = make_set(AppDisplayName),
        ResourceDisplayName = make_set(ResourceDisplayName),
        ClientAppUsed = make_set(ClientAppUsed),
        take_any(ResultType, ResultDescription, UserAgent, AutonomousSystemNumber, DeviceDetail_dynamic, DeviceDetail_string),
        take_anyif(UserPrincipalName, UserPrincipalName != UserId),
        take_anyif(UserDisplayName, UserDisplayName != UserId),
        take_anyif(Location, isnotempty(Location))
        by Category, UserId, IPAddress
    // Remove expected IP addresses
    | where not(isnotempty(parse_ipv4(IPAddress)) and ipv4_is_in_any_range(IPAddress, _ExpectedIPAddresses))
    | lookup kind = leftouter (
        AuditLogs
        | where TimeGenerated > ago(query_period)
        | where Category == "UserManagement" and OperationName has "Disable account" and Result == "success"
        | project
            AADDisable_TimeGenerated = TimeGenerated,
            UserId = tostring(TargetResources[0]["id"])
        ) on UserId
    | lookup kind = leftouter (
        IdentityInfo
        | where TimeGenerated > ago(query_period)
        | summarize arg_max(TimeGenerated, AccountSID) by AccountObjectId
        | project
            AccountSID,
            AccountObjectId
        ) on $left.UserId == $right.AccountObjectId
    | lookup kind = leftouter (
        SecurityEvent
        | where TimeGenerated > ago(query_period)
        | where EventID == 4725
        | summarize arg_min(TimeGenerated, *) by Computer, SubjectLogonId, TargetSid
        | project
            ADDisable_TimeGenerated = TimeGenerated,
            TargetSid
        ) on $left.AccountSID == $right.TargetSid
    | where not((isnotempty(ADDisable_TimeGenerated) and ADDisable_TimeGenerated > ago(query_period))
        or (isnotempty(AADDisable_TimeGenerated) and AADDisable_TimeGenerated > ago(query_period)))
    | extend
        DeviceDetail = iff(isnotempty(DeviceDetail_string), todynamic(DeviceDetail_string), DeviceDetail_dynamic),
        ParsedUserAgent = parse_user_agent(UserAgent, dynamic(["os", "browser"]))
    | extend
        DeviceId = tostring(DeviceDetail["deviceId"]),
        DeviceName = tostring(DeviceDetail["displayName"]),
        DeviceIsManaged = tostring(DeviceDetail["isManaged"]),
        DeviceTrustType = tostring(DeviceDetail["trustType"]),
        DeviceDetailOS = tostring(DeviceDetail["operatingSystem"]),
        UserAgentOS = tostring(ParsedUserAgent["OperatingSystem"]["Family"]),
        Browser = tostring(ParsedUserAgent["Browser"]["Family"])
    | extend
        OperatingSystem = case(
            isempty(DeviceDetailOS), UserAgentOS,
            isempty(UserAgent), extract(@"^([A-Za-z]+)", 1, DeviceDetailOS),
            UserAgentOS == "Other", extract(@"^([A-Za-z]+)", 1, DeviceDetailOS),
            UserAgentOS
        ),
        Browser = case(
            Browser == "Other", iff(UserAgent != "-", extract(@"^([^\/\s]+)", 1, UserAgent), ""),
            Browser
        )
    | extend
        OperatingSystem = case(
            OperatingSystem has "ios", "iOS",
            OperatingSystem has_any ("mac", "macos"), "macOS",
            OperatingSystem == "Ubuntu", "Linux",
            Browser == "Samsung Internet", "Android",
            Browser == "MacOutlook", "macOS",
            OperatingSystem
        )
;
let _MultipleUserAddresses = toscalar(
    _DisabledAttempts
    | summarize UserIds = make_set(UserId) by IPAddress
    | where array_length(UserIds) > 1
    | summarize make_list(IPAddress)
);
_DisabledAttempts
| where not(not(IPAddress in (_MultipleUserAddresses)) and Location in (_ExpectedLocations) and AutonomousSystemNumber in (_ExpectedASNs) and (OperatingSystem in ("Android", "iOS") or isnotempty(DeviceTrustType)))
| extend IPRange = parse_ipv6_mask(IPAddress, 128 - (32 - ipv4_prefix_mask))
| extend HexCodes = split(extract(@"^(?i:0+\:0+\:0+\:0+\:0+\:ffff\:([a-f0-9]+\:[a-f0-9]+))$", 1, IPRange), ":")
| extend
    IPRange = todynamic(tostring(split(case(
        array_length(HexCodes) == 2, format_ipv4_mask(tolong(strcat("0x", tostring(HexCodes[0])))*65536 + tolong(strcat("0x", tostring(HexCodes[1]))), ipv4_prefix_mask),
        IPRange
    ), "/", 0)))[0],
    IPRangeAddressScope = case(
        array_length(HexCodes) == 2, ipv4_prefix_mask,
        128 - (32 - ipv4_prefix_mask)
    )
| project
    TimeGenerated,
    Category,
    ResultType,
    ResultDescription,
    UserPrincipalName,
    UserDisplayName,
    IPAddress,
    Location,
    AppDisplayName,
    ResourceDisplayName,
    ClientAppUsed,
    UserAgent,
    DeviceDetail,
    DeviceId,
    DeviceName,
    DeviceIsManaged,
    DeviceTrustType,
    OperatingSystem,
    Browser,
    AutonomousSystemNumber,
    IPRange,
    IPRangeAddressScope,
    UserId

Explanation

The query is retrieving disabled account attempts from various sources and filtering out expected IP addresses, locations, and autonomous system numbers. It also extracts information about the device and user associated with the disabled account attempts. The final result includes details such as the time generated, category, result type, user principal name, IP address, location, device details, operating system, browser, and more.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: August 22, 2023

Tables

SigninLogsAADNonInteractiveUserSignInLogsAuditLogsIdentityInfoSecurityEvent

Keywords

Keywords:let,query_frequency,query_period,ipv4_prefix_mask,_ExpectedIPAddresses,_ExpectedLocations,_ExpectedASNs,_DisabledAttempts,isfuzzy,SigninLogs,AADNonInteractiveUserSignInLogs,TimeGenerated,ResultType,ResultDescription,UserAgent,AutonomousSystemNumber,DeviceDetail_dynamic,DeviceDetail_string,UserPrincipalName,UserDisplayName,Location,Category,UserId,IPAddress,AuditLogs,OperationName,Result,AADDisable_TimeGenerated,TargetResources,IdentityInfo,AccountSID,AccountObjectId,SecurityEvent,EventID,Computer,SubjectLogonId,TargetSid,ADDisable_TimeGenerated,DeviceDetail,ParsedUserAgent,DeviceId,DeviceName,DeviceIsManaged,DeviceTrustType,DeviceDetailOS,UserAgentOS,Browser,OperatingSystem,IPRange,HexCodes,IPRangeAddressScope,TimeGenerated,Category,ResultType,ResultDescription,UserPrincipalName,UserDisplayName,IPAddress,Location,AppDisplayName,ResourceDisplayName,ClientAppUsed,UserAgent,DeviceDetail,DeviceId,DeviceName,DeviceIsManaged,DeviceTrustType,OperatingSystem,Browser,AutonomousSystemNumber,IPRange,IPRangeAddressScope,UserId

Operators

letuniontoscalarsummarizemake_listwherebyarg_minmake_settake_anytake_anyifisnotemptyparse_ipv4ipv4_is_in_any_rangelookupleftouterprojectiffextendparse_user_agentextractcaseisemptyhashas_anyinarray_lengthsplitformat_ipv4_masktolongformat_ipv4_masktodynamictostringtolongformat_ipv4_maskproject

Actions