Hunting for Basic Authentication in AzureAD
Hallo zusammen,
Im Exchange Team Blog wurde bereits vor fast einem Jahr die Abschaltung der Basic Authentication für die zweite Jahreshälfte 2021 angekündigt. Es wird also Zeit, die Applikationen und Clients mit Basic Authentication zu jagen und auf neuere Authentication Methoden umzustellen.
Basic Authentication and Exchange Online – April 2020 Update
https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-and-exchange-online-april-2020-update/ba-p/1275508
Gerade als ich den Artikel geschrieben habe, gab es ein Update:
Basic Authentication and Exchange Online – February 2021 Update
https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-and-exchange-online-february-2021-update/ba-p/2111904
In Azure Active Directory unter Sign-Ins kann man mit "Add Filter" nach "Client App" filtern.
Nun werden die ClientApps mit Legacy Authentication (sprich: Basic Auth) angezeigt.
Im letzten Monat hat sich zweimal der ewservice@icewolf.ch versucht mit Basic Auth anzumelden. Einmal erfolgreich und einmal mit falschem Passwort.
Ich speichere die Azure AD Logs in einem LogAnalytics Workspace
Seit ein paar Monaten gibt es hier neue Logs, wie die NonInteractiveSignInLogins, ServicePrincipalSignInLogs, ManagedIdentitySingnInLogs und die ProvisioningLogs.
Unter den Logs sieht man genau diese Tabellen.
Nun durchforste ich mit einem KQL Query die SignInLogs. Wie man sieht, gibt es für den user ewservice@icewolf.ch zwei Logins. ResultType = 0 heisst, die Anmeldung war erfolgreich.
SigninLogs | where UserPrincipalName == ewservice@icewolf.ch
Mit einem Advanced Query, wird die Abfrage um eine virtuelle Spalte "isLegacyAuth" erweitert und in der where Klausel suche ich nach BasicAuth und erfolgreichen Anmeldungen. Ich summiere das ganze für den ausgewählten Zeitraum nach UserprincipalName und AppDisplayName.
let data = SigninLogs
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes")
| where isLegacyAuth == "Yes" and ResultType==0;
data
| summarize count() by UserPrincipalName, AppDisplayName
//Legacy Auth
SigninLogs
| where ResultType==0
| where ClientAppUsed == "Autodiscover" or ClientAppUsed == "Exchange ActiveSync" or ClientAppUsed == "Exchange Online Powershell" or ClientAppUsed == "Exchange Web Services" or ClientAppUsed == "IMAP" or ClientAppUsed == "POP3" or ClientAppUsed == "MAPI over HTTP" or ClientAppUsed == "Offline Address Book" or ClientAppUsed == "Other Clients" or ClientAppUsed == "Outlook Anywhere (RPC over HTTPS)" or ClientAppUsed == "POP" or ClientAppUsed == "Reporting Web Services" or ClientAppUsed == "SMTP" or ClientAppUsed == "Universal Outlook"
//| summarize count() by UserPrincipalName, AppDisplayName
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
//Legacy Auth ohne ActiveSync
SigninLogs
| where ResultType==0
| where ClientAppUsed == "Autodiscover" or ClientAppUsed == "Exchange Online Powershell" or ClientAppUsed == "Exchange Web Services" or ClientAppUsed == "IMAP" or ClientAppUsed == "POP3" or ClientAppUsed == "MAPI over HTTP" or ClientAppUsed == "Offline Address Book" or ClientAppUsed == "Other Clients" or ClientAppUsed == "Outlook Anywhere (RPC over HTTPS)" or ClientAppUsed == "POP" or ClientAppUsed == "Reporting Web Services" or ClientAppUsed == "SMTP" or ClientAppUsed == "Universal Outlook"
//| summarize count() by UserPrincipalName, AppDisplayName
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
//Sucessful POP3 / IMAP Logins
SigninLogs
| where TimeGenerated > ago(30d)
| where ClientAppUsed == "IMAP4" or ClientAppUsed == "POP3"
| where ResultType == 0
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
//Unsucessful POP3 / IMAP Logins
SigninLogs
| where TimeGenerated > ago(30d)
| where ClientAppUsed == "IMAP4" or ClientAppUsed == "POP3"
| where ResultType != 0
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
// Sucessful EWS Logins
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType==0
| where ClientAppUsed == "Exchange Web Services"
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
// Sucessful SMTP Auth
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType==0
| where ClientAppUsed == "Authenticated SMTP"
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
// Sucessful Logins (without POP3/IMAP/EWS/SMTPAuth)
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType==0
| where ClientAppUsed == "Autodiscover" or ClientAppUsed == "Exchange Online Powershell" or ClientAppUsed == "MAPI over HTTP" or ClientAppUsed == "Offline Address Book" or ClientAppUsed == "Other Clients" or ClientAppUsed == "Outlook Anywhere (RPC over HTTPS)" or ClientAppUsed == "Reporting Web Services" or ClientAppUsed == "Universal Outlook" or ClientAppUsed == "Exchange ActiveSync"
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
Da ich schon dabei bin, habe ich mir auch die anderen Logs angeschaut.
Oder in der Tabelle AADServicePrincipalSignInLogs nach einer mir bekannten AppID gesucht.
AADServicePrincipalSignInLogs | where AppId == "9a8d72df-686a-496c-bc5e-a147d813abd1"
let data = SigninLogs
| where AppDisplayName in ('*') or '*' in ('*')
| where UserDisplayName in ('*') or '*' in ('*')
| extend errorCode = toint(Status.errorCode)
| extend SigninStatus = case(errorCode == 0, "Success",
errorCode == 50058, "Interrupt",
errorCode == 50140, "Interrupt",
errorCode == 51006, "Interrupt",
errorCode == 50059, "Interrupt",
errorCode == 65001, "Interrupt",
errorCode == 52004, "Interrupt",
errorCode == 50055, "Interrupt",
errorCode == 50144, "Interrupt",
errorCode == 50072, "Interrupt",
errorCode == 50074, "Interrupt",
errorCode == 16000, "Interrupt",
errorCode == 16001, "Interrupt",
errorCode == 16003, "Interrupt",
errorCode == 50127, "Interrupt",
errorCode == 50125, "Interrupt",
errorCode == 50129, "Interrupt",
errorCode == 50143, "Interrupt",
errorCode == 81010, "Interrupt",
errorCode == 81014, "Interrupt",
errorCode == 81012, "Interrupt",
"Failure")
| where SigninStatus == '*' or '*' == '*' or '*' == 'All Sign-ins'
| extend Reason = tostring(Status.failureReason)
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes")
| where isLegacyAuth == "Yes"
| where AppDisplayName in ('*') or '*' in ('*')
| where details.Type == '*' or (details.Type == 'App' and AppDisplayName == details.Name) or (details.Type == 'Protocol' and AppDisplayName == details.ParentId and ClientAppUsed == details.Name);
data
| top 200 by TimeGenerated desc
| extend TimeFromNow = now() - TimeGenerated
| extend TimeAgo = strcat(case(TimeFromNow < 2m, strcat(toint(TimeFromNow / 1m), ' seconds'), TimeFromNow < 2h, strcat(toint(TimeFromNow / 1m), ' minutes'), TimeFromNow < 2d, strcat(toint(TimeFromNow / 1h), ' hours'), strcat(toint(TimeFromNow / 1d), ' days')), ' ago')
| project User = UserDisplayName, ['Sign-in Status'] = strcat(iff(SigninStatus == 'Success', '✔️', '❌'), ' ', SigninStatus), ['Sign-in Time'] = TimeAgo, App = AppDisplayName, ['Error code'] = errorCode, ['Result type'] = ResultType, ['Result signature'] = ResultSignature, ['Result description'] = ResultDescription, ['Conditional access policies'] = ConditionalAccessPolicies, ['Conditional access status'] = ConditionalAccessStatus, ['Operating system'] = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser, ['Country or region'] = LocationDetails.countryOrRegion, ['State'] = LocationDetails.state, ['City'] = LocationDetails.city, ['Time generated'] = TimeGenerated, Status, ['User principal name'] = UserPrincipalName