A brief history
Seamless Single Sign On was first introduced in late 2016 and provided a way for users to authenticate to Entra ID (Azure AD at the time) using a Kerberos ticket from Active Directory. Unfortunately, most environments had it enabled at one point or another thanks to one of the worst default options still prevalent in Entra today - because not only is it enabled by default, it is extremely unclear as to what this option does:
For a short but excellent explanation of how Seamless SSO works, I highly recommend the article below by Steve Syfuhs. In fact, any time you want a concise and enjoyable read on how something related to Microsoft authentication works, I suggest Steve’s blog as a first stop ;)
https://syfuhs.net/2017/03/19/a-look-at-azure-ad-single-sign-on/
As one can imageine, a lot has changed in almost 9 years since its release, and this feature has moved from a convenient user feature to an unnecessary security liability (with limited exceptions)… Early on, it was understood that an attacker who obtained the password for the AZUREADSSOACC computer account could impersonate [the first factor of] any cloud user including synced admins with privileged roles in Entra. Later in 2021, Secureworks published research finding this feature could be used for account enumeration, brute force, and credential stuffing attacks.
https://www.secureworks.com/research/undetected-azure-active-directory-brute-force-attacks
This new finding added urgency to getting Seamless SSO disabled, and while Microsoft fixed some unnecessary exposure and logging shortcomings, they can’t fix the inherent risks for those who still have it enabled. Even before this discovery, it was advised that organizations move to disable Seamless SSO because it was no longer necessary since devices that are registered or joined to Entra ID could get Primary Refresh Tokens removing the need for this feature.
In fact, Seamless SSO also comes with attack surface, so you really should work toward removing anything that is using it to remove risk
— Nathan McNulty (@NathanMcNulty) August 29, 2024
Windows - PRT via Entra join, Hybrid join, or registered
macOS - PRT via Enterprise SSO plug-in
iOS/Android - PRT via registration/broker app https://t.co/ydEFPxmcpI pic.twitter.com/OAXE85lmV8
Unfortunately, many places still have not disabled Seamless SSO due to the age old problem of IT - “What if it breaks something?”
Finding Seamless SSO
Unfortunately, Microsoft provides no documentation or guidance on how to identify Seamless SSO usage from Entra ID Sign-in logs. The only hint we get is that this feature generates an event on Domain Controllers if Advanced Audit Policies for Kerberos has not been disabled:
Hopefully you have centralized logging of the Security logs from Domain Controllers, but if not, we would have to go to each Domain Controller and use this filter against the Security log to identify Kerberos ticket requests:
While this most likely indicates usage, that is not always guaranteed if outbound filtering or a client side configuration issue is breaking the endpoint the Kerberos ticket is supposed to be sent to (an issue I’ve seen a few times). Regardless of use, identifying devices making requests is a great starting point for figuring out where Seamless SSO might still be in use.
A special thanks to Bastien Perez (@bastienperez_), Russell Adams (@ruadams), and a few others in helping me with testing various methods of finding usage :)
Entra Sign-in Logs
What we really need is to find the authentication event in Entra ID, but Microsoft never tells us what that is supposed to look like. In order to discover what this looks like, we’ll have to set up a user who hasn’t logged in for a while, perform an authentication using Seamless SSO, and then review the logs. And I’ve done just that so you don’t have to :)
When testing like this, I like to go to the user in Entra, then click on Sign-in logs, and then we can review with that filter. Unfortunately, this event does not show up in the “Interactive” sign-in logs, and instead, you will need to go to the “Non-interactive” sign-in logs like this:
The aggregated view of these logs makes it a little more difficult to see what the most recent events were, but it does help consolidate all the events per each app making it easier to look at one app at a time.
In this case, we look through the various events and find ourselves with a sign-in that looks weird - single factor, no application name or id, no client app, lots of other details missing, and a resource of “Windows Azure Active Directory”:
With this information in hand, we can now (sort of) filter the Non-interactive sign-in logs to this resourceId (00000002-0000-0000-c000-000000000000
) to find more use. The issue we run into here is that this is used for more than just Seamless SSO, and the GUI does not allow us to filter on missing values, such as no appId or no authenticationDetails…
For a spot check, this should be sufficient to use the UI to determine if Seamless SSO is in use or not - find events where there is no Application Name/ID by standard users (not Entra Connect Sync accounts), and you can also look for no values in the Authentication details tab:
Graph API
Now that we know what this event looks like, we can query the Graph API logs for this activity. Since these are non-interactive sign-in events, we will need to use the Beta API endpoint and specify signInEventType in our filter query:
In addition to this filter, we also want to limit the results returned to only those targeting the resourceId we discovered in the GUI, and just like the GUI, I have not been able to find a way to filter for empty values on AppId or AuthenticationDetails. This means we’ll have to pull back all of the events and filter afterwards, and this will take a very long time in larger environments - go grab a coffee or something ;)
The resulting Graph PowerShell will look like this:
1$events = @()
2$uri = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=signInEventTypes/any(t: t eq 'nonInteractiveUser') and resourceId eq '00000002-0000-0000-c000-000000000000'"
3do {
4 $response = Invoke-MgGraphRequest -Method GET -Uri $uri
5 $events += $response.value
6 $uri = $response.'@odata.nextLink'
7} while ($uri)
8$events | Where-Object { $_.appId -eq "" -and $_.authenticationDetails.succeeded -ne "true"} | Measure-Object
9$events | Where-Object { $_.appId -eq "" -and $_.authenticationDetails.succeeded -ne "true"}
Kusto Query Language
If you are lucky enough to have Defender XDR or ingest Non-Interactive Sign-in logs to Log Analytics, you can query against these using KQL. Unlike the GUI or Graph API, it is possible to query for empty values and arrays here, and this makes our query a little simpler :)
1AADNonInteractiveUserSignInLogs
2| where AppId == ""
3| where AuthenticationDetails == @"[]"
Defender for Identity
Note
A huge thanks to Robbe Van den Daele for this contribution! :)
If the DCs are onboarded in MDI, you can actually query Kerberos logins using IdentityLogonEvents. I was able to identify Seamless SSO usage by using the below query:
1// Get all device info we can find
2let devices = (
3 DeviceInfo
4 // Search for 14 days
5 | where TimeGenerated > ago(14d)
6 // Normalize DeviceName
7 // --> if it is an IP Address we keep it
8 // --> If it is not an IP Address we only use the hostname for correlation
9 | extend DeviceName = iff(ipv4_is_private(DeviceName), DeviceName, tolower(split(DeviceName, ".")[0]))
10 // Only get interesting data
11 | distinct DeviceName, OSPlatform, OSVersion, DeviceId, OnboardingStatus, Model, JoinType
12);
13IdentityLogonEvents
14// Get the last 30 days of logon events on Domain Controllers
15| where TimeGenerated > ago(30d)
16// Search for Seamless SSO events
17| where Application == "Active Directory" and Protocol == "Kerberos"
18| where TargetDeviceName == "AZUREADSSOACC"
19// Save the domain name of the Domain Controller
20| extend OnPremisesDomainName = strcat(split(DestinationDeviceName, ".")[-2], ".", split(DestinationDeviceName, ".")[-1])
21// Normalize DeviceName
22// --> if it is an IP Address we keep it
23// --> If it is not an IP Address we only use the hostname for correlation
24| extend DeviceName = iff(ipv4_is_private(DeviceName), DeviceName, tolower(split(DeviceName, ".")[0]))
25// Only use interesting data and find more info regarding the source device
26| distinct AccountUpn, OnPremisesDomainName, DeviceName
27| join kind=leftouter devices on DeviceName
28| project-away DeviceName1
29// Check if Seamless SSO usage is expected
30| extend ['Seamless SSO Expected'] = case(
31 // Cases where we do not expect Seamless SSO to be used
32 JoinType == "Hybrid Azure AD Join" or
33 JoinType == "AAD Joined" or
34 JoinType == "AAD Registered", "No",
35 // Cases where we do expect Seamless SSO to be used
36 JoinType == "Domain Joined" or
37 (OSPlatform startswith "Windows" and toreal(OSVersion) < 10.0) , "Yes",
38 // Cases that need to be verified
39 "Unknown (to verify)"
40)
The advantage of this method is by extracting the Domain Name of the DestinationDeviceName (the domain controller), you can identify in which AD Domains Seamless SSO is enabled. Something I was not able to do using the EntraID SignIn logs (since the domains in AccountUPNs can differ).
Disabling Seamless SSO
Now that we’ve identified if / where Seamless SSO is in use, the goal is to see if we can remove use by providing better options such as registering or joining devices to Entra to get Primary Refresh Tokens. The only area we truly still need Seamless SSO is for non-persistent VDI where the VDI software we are using does not support instant hybrid join. Both Citrix and VMware have provided solutions for this in recent years, so it’s worth double checking if your version is supported.
Rather than making this article even longer, I recommend you check Daniel Bradley’s great article that already has the step by step process to disable Seamless SSO:
https://ourcloudnetwork.com/why-you-should-disable-seamless-sso-in-microsoft-entra-connect/