Microsoft 365 enumeration, spraying and exfiltration - TeamFiltration in the spotlight
TeamFiltration is self-defined as a cross-platform framework for enumerating, spraying, exfiltrating, and backdooring O365 AAD accounts.
In this article, we will look at its capabilities and how we can potentially detect related events in Azure AD and Microsoft 365 logs. While the article focuses on TeamFiltration, the learnings apply to any similar toolset.
TeamFiltration is a framework developed by @flangvik from TrustedSec which allows to perform reconnaissance and gain initial access to Microsoft 365 and Azure AD tenants in order to potentially exfiltrate data or open the door to other post-exploitation activities.
This article is based on TeamFiltration 3.5.0
Purpose of this article
Enumeration, spraying and brute-forcing are common attack techniques for initial access (TA0001 - Initial Access), specifically on Cloud workloads. While there are loads of interesting articles on post-exploitation frameworks (albeit, not that much on Cloud post-exploitation), “post-exploitations”, by definition, means there was an initial access. Detecting initial access attacks and analyzing relevant logs in Azure AD is what we attempt to do in this article.
TeamFiltration is of course not the only framework available for enumerating or brute-forcing Azure Active Directory (e.g.: AADInternals, PowerZure, Microburst, MSOLSPray and many more) but it allows to chain multiple techniques, some of them being interesting from a detection perspective. It is also because of the interesting premises of the TeamFiltration framework, which are based on the fact that Microsoft relies on a specific “version” of the OAuth flow (On-Behalf of flow) for some of its office applications, in order for instance to allow Teams to have access to your Outlook calendar or your OneDrive files (more of that on the below talk).
We will divide this article in several sections, dissecting each one of the technique offered by TeamFiltration and looking at the corresponding detection in the logs.
TeamFiltration is written in C# and can either be used from a standalone build version or by compiling sources from the corresponding GitHub repository. I encourage you to watch the presentation of the framework @Def Con 30 - Taking a Dump in the Cloud for an onveriew of the framework, its foundations and a demo by the author.
TeamFiltration has 3 main modules: enumeration, spraying and exfiltration.
A brief on logs used in this article
We will focus on the main Azure AD logs and the Microsoft 365 Unified Audit Log (UAL) to observe logging events related to usage of TeamFiltration. Indeed, these logs are found in most Microsoft customers tenants without too much hassle. In our case, these logs are ingested into Microsoft Sentinel, but any SIEM or log concentrator could be used. You can also directly browse the logs from the Microsoft Entra portal or from Defender 365 portal for instance.
Azure AD Logs
- Sign-in Logs: interractive logins attempts
- Non-interractive Sign-in Logs: non-interractive logins attempts
Audit, Service principal, Provisionning or managed identity logs will not bring any value in this context.
- OfficeActivity (if using Microsoft Sentinel)
A note on Fireprox
TeamFiltration does not trigger enumerations or spraying directly from the client IP where you execute it, it is a bit more clever and leverages a proxy utility called Fireprox. Fireprox generates pass-through proxies that rotate the source IP address with every request, leveraging the AWS API gateway behind the scenes. More information can be found here. When you execute TeamFiltration, it will actually first create a temporary AWS API gateway and corresponding endpoints.
Example for enum and –validate-msol technique:
When called, this endpoint will simply forward requests (as a proxy) to
This allows users of TeamFiltration in this case to “hide” their real IPs when scanning and rotate automatically though IPs from AWS public IP range, hence avoiding a simple IP block by the target.
From a detection standpoint, however, this also means that enumeration and spraying from AWS public IP ranges will be a signature of Teamfiltration, in its current and vanilla version.
Note: the framework might evolve in the future and bring similar capabilities on Azure or GCP or even bring VPS capabilities in, but in the current state, AWS public ip ranges with sign-in events could be a good IOC to build on as we will see in the following sections. As the framework is open-source, attackers could change or remove the user of Fireprox to adapt to their own infrastructures/needs.
TeamFiltration enumeration has three parameters which allow to enter a domain for the target organization (example contoso.net), adding an optional list of usernames (common usernames or for instance a list gathered using OSINT, Google, LinkedIn, a company portal or other sources) or, yet, using the dehashed (a database of compromised assets such as email accounts). The enumeration module offers three enumeration types: MSOL, Teams or regular logins attempts.
Enumeration using MSOL (Microsoft Online module)
This enumeration is using a known technique based on the login.microsoftonline.com API, which is simply the API used by all users to login to Office 365.
If you authenticate to https://www.office.com/, you will be able to see the OAUTH flow leveraging this specific API.
The exact method used by this enumeration technique is GetCredentialType which gives login information, including Desktop SSO information.
What TeamFiltration will do, when you use this method, is generating a random username (in the domain of the target organization), and validating it against GetCredentialType.
This technique has been covered multiple times in the past. This API method expects several parameters, we can see this by simply fuzzing the endpoint with Postman and a valid username to start with:
If we compare with an account which does not normally exists on this domain:
The notable difference is on “IfExistsResult” where it will respectively be 0 or 1 depending if the user exists in the tenant or not. It directly shows you how easy it is to enumerate accounts using this method.
This method (GetCredentialsType) will not work on each tenant, depends on specifics (managed or federated domains for instance), and might give some false-positives. This is not the purpose of this article, however the current way TeamFiltration (as of v3.5.0) does validate usage of this method is also incorrect (author acknowledged this and this will surely be fixed soon).
This is indeed the problem with undocumented APIs, the specification did evolve and thus the response payload did change.
This leads to –validate-msol method resulting in failure message: this method is not supported for the target tenant.
Note that this method is throttled by Microsoft and hence slow if enumerating on a big user list.
When you issue the –enum-msol command with a target domain, TeamFiltration will ask you for an expected email format, so it can then “brute-force” enumeration based on a list of common names (John Smith, Sarah Parker…), pulled from statistically-likely-usernames (typically, the target tenant we use has a user called John Smith, which would be a direct match) or based on a potential list of usernames which you pass as input:
Note: When you do enumeration, TeamFiltration is building up a database of previous attempts, and will skip the usernames it already tried to enum for this specific domain (database is in the –outpath parameter). As you notice and as discussed above, the method is not supported by TeamFiltration on our target tenant.
One of the problem for blue teams when attackers are using undocumented APIs (understand: APIs used by known applications or websites but not documented for development or customer usage, to the opposite of APIs such as Graph API) is that most of the time, they won’t be visible inside your logs or not in the way you’d expect. Moreover, in this case, there is no login attempt, it is just enumeration. If the authentication happens successfully, sign-in logs will of course appear in Azure AD but the GetCredentialsType API will not trigger any.
Enumeration using Teams (Microsoft Teams APIs)
This enumeration method is the ‘core’ of the research presented at Defcon, as the author did an extensive analysis of how Teams authentication works and what APIs are called by the Teams client. You can see from the Defcon presentation that Teams uses quite a lot of APIs. This method is leveraging the Teams search API to search users cross-tenant.
Hint for blues: you can limit the cross-tenant search in Teams Administration.
This method does require a so-called sacrificial O365 account, which is simply an Office 365 user account with at least Teams enabled, in order to do cross-tenant enumeration.
Of course you could also imagine that an attacker use a single compromised account from your own organization to do the same, hence removing the need for the cross-tenant setting, but potentially raising more flags in terms of detection and, at the end, if you have already an account in the tenant, there are many other ways to enumerate users.
In this test, I used a sacrificial account in a test tenant I own and this time provided a list of usernames (which, again attackers usually easily scrap from many sources) rather than using the brute-force enumeration available in the tool (based on the Github list of common names).
You can see that the tool successfully validated 5 accounts from the list passed as input.
So this one I can detect right? Eh…no.
This is again just enumeration using a valid cross-tenant search feature. If we look at UAL or sign-in logs or yet non-interractive sign-in Logs, there is no trace of such enumeration. Remember, there is no sign-in attempt yet.
One could argue for the possibility to log cross-tenant Teams searches in the Unified Audit Logs (UAL), but imagine the number of logs it could generate, for little value at the end.
Enumeration using Logins
The last available method is the simplest and of course most useful one from a detection standpoint. It actually tries to login to see if the user exists or not.
It will trigger sign-in attempts, like you would do by simply browsing to the Office365 portal for instance. It then uses a random password to test if the user exists or not.
This method is of course the only enumeration method proposed by TeamFiltration which we can detect as this basically attempt to login with either the provided list of usernames, either by attempting the list of common usernames described above. We are interested therefore in interractive sign-in logs of Azure AD.
There are interesting details in the logs:
- Location: in this case it is France, but next time it might be US, Fireprox will indeed create the AWS endpoints in a random region, so this is not a IOC you could use. However, the fact that these locations might be rare in your company and rolling for each enumeration attempts, might give detection opportunities.
- Authentication Requirements: You can see if account enumerated has MFA enforced or not, which could higher the related incident severity
- Conditional Access: This was not applied, as the login attempt failed at first factor
- IP Address: This is a public IP from AWS public IP range, could be a completely different one next time, still in AWS range
- Device detail: there is no details about the device and no user-agent mentionned
Why is there no user-agent and what would be the expected one?
The user-agent is defined in the configuration file of TeamFiltration. However, if you indicates an agent which Azure AD cannot match, it will just be empty in the sign-in logs. Here is the configuration file used for this attempt:
If we do the same attempt, with a known/valid agent:
So this is a bug in the way Azure AD logs handle the user-agent string, as an invalid or empty agent will lead to an empty DeviceDetails field in the logs.
Do notice as well the changes of IP Address and Location.
This being said, as you might know, Azure AD is tighly integrated to Defender 365, and two new tables are available on Defender 365 advanced hunting blade (see AADSignInEventsBeta and the same for Service Principals, AADSpnSignInEventsBeta).
As indicated in the documentation, these tables are temporary and all sign-in schema information will eventually move to the IdentityLogonEvents table (which is a Microsoft Defender for Identity log source originally). We can therefore expect the same in Microsoft Sentinel.
First you can see that this new table contains both non-interractive and interractive sign-ins, but also that the available information is formatted a bit differently:
Let’s look at the logs where user-agent was a non-existing one, it is now visible in the logs:
If no agent is specified in Teamfiltration, in the current version, the default String will be: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.30866 Chrome/80.0.3987.165 Electron/8.5.1 Safari/537.36”, which corresponds to Chrome browser: see here.
Wait, what with the different Application IDs and Application Display Names? (in green)
Good catch! In fact, TeamFiltration does not always use the same APIs in this enum method, and rotate between several (hardcoded) APIs, but more specifically it enforces specific Application IDs which correspond to specific Microsoft applications in any Azure AD tenant.
The full list of applications and application IDs of Microsoft first-party applications can be found hbere: https://learn.microsoft.com/en-us/troubleshoot/azure/active-directory/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications.
APIs used (at time of writing):
With the following Application ID, “1fec8e78-bce4-4aaf-ab1b-5451cc387264”, which if you look in Azure AD, like confirmed by the above list, corresponds to Microsoft Teams (it will be the same GUID for all Azure AD tenant):
With the following Application ID, “d3590ed6-52b3-4102-aeff-aad2292ab01c”, which corresponds to Microsoft Office.
With the following Application ID, “ab9b8c07-8f02-4f72-87fa-80105867a763”, which corresponds to OneDrive SyncEngine.
Of course the tool will evolve, and could rotate between all application IDs available here, so detections should not be limited to the current list.
Wait a minute…
In the sign-in logs, when account is not locked, error code is invalid username or password, how does TeamFiltration knows these are valid accounts? Well because in fact if the account does not exist at all, the response issued by the above APIs will be different. Let’s try with https://outlook.office365.com:
Notice, it uses the GetCredentialType we discussed at the beginning? now for a valid account:
Hey, it did not even use GetCredentialsType here? Good catch! But is just because my browser had some cached credentials for this one, if I clear the cache and start again:
A note on the embedded database
TeamFiltration has an embedded database, which allows to store all enum, spraying or exfiltration attempts locally. If we look into it, we can see the results of our attempts which can be further used in the spraying module for instance!
Let’s now dive into the tool and analyse the spraying part!
We had a good overview of the enumeration capabilities but enumeration on its own is pointless, the goal is to generally try to gain access (unless you just want to sell phishing/scam data) and spraying is an efficient way to do so.
If you want to know more about password spraying and how to prevent it on Azure AD, have a look here.
Available spraying methods
TeamFiltration proposes two main methods and several options for password spraying:
- Default method: using standard login method (regular user sign-ins)
- Method 2: AAD SSO: this is a method discovered by SecureWorks, described here, based on Azure AD single-sign on, and exploiting the target URI: https://autologon.microsoftazuread-sso.com//winauth/trust/2005/windowstransport.
Let’s explore these two methods.
There are several options, we will not test or explain all of them here, but here is a short summary:
- The golden rule when you spray is low-and slow, and the default TeamFiltration config is to sleep minimum 60 minutes between each round of attempts and maximum 100. Understand by that, that TeamFiltration will attempt the list of enumerated usernames with a first password, then sleep before moving to the next password attempt. I used –force here for obvious reasons, to skip the time thresholds, but this of course is risky and will lead to locked accounts (see details on accounts locking here). The “recurring” spraying pattern, albeit changeable, also means a pattern for detection but TeamFiltration nicely do the next attempts at a random time between the minimum and maximum thresholds defined
- You can define a time window when spraying should occur, for instance during business hours, to trigger less anomalies
- You can input a list of passwords, from a dictionnary, which is recommended. If you don’t TeamFiltration generates a list of common passwords from a combination of months, years and generic words. There are several other options like top 20 common passwords for instance. You can also use a list of ‘combos’, meaning a list of username:password
- Next to time windows for spraying, you can define the default delay between attempts
- There is an integration with pushover to get notifications about successful sprays, so an attacker could have big time windows and delays between attempts, running for months and be notified of a success
Let’s have two real attempts now, where we input a password list from a dictionnary of top 10000 common passwords:
…and yes playing with fire, reducing the time between sprays too much and you just locked all accounts.
Let’s do the same, but for the purpose of this article to be finished one day, just with 5 passwords in the dictionnary and a bit bigger delay:
You will note we locked some accounts in the process, because normally spraying should be low-and-slow, but we found a valid password on an account with no MFA! We also found one valid password for a MFA-enabled account, this could still be interesting for phishing or MFA fatigue type of attacks.
For the second method:
Same principle but using a different method:
We got AADSTS81016 as a response to all attempts, because this tenant does not use Seamless SSO (see SecureWorks article, and more details here on Seamless SSO).
The two methods will trigger different logs: one is interractive, the other one relies on non-interractive sign-ins, with of course, in both cases, Fireprox being used with fresh instances for each attempt, and hence new IPs from AWS public IP ranges.
Method 1, interractive:
We can see the results of the sprays in the sign-in logs, along with the failures, account lockouts but also the API (through the application name or id) TeamFiltration used, as it is rotating between several ones, like discussed previously.
Another interesting point, for one account we triggered a CA policy, which elevated the authentication requirement to MFA. You could also use such a tool to blueprint CA policies, if no login option is available. Indeed, the reponse from TeamFiltration was “Valid but MFA (76)” which corresponds to Result Code 50076 in our logs:
CA policies are complex and often have flaws. Sometimes, MFA is enforced in all scenarios except for specific accounts or applications. BYOD is a good recurring example we see out there.
Method 2, non-interractive:
In our case, this method will fail due to users and this lab being Cloud-only but we can still see attempts in non-interractive sign-ins this time. We also note that missing Application Name or ID and the ResultType being ‘Other’, because the method failed for that tenant:
Ok if your spraying was successful or are able to find a valid account and credentials through any other mean, you want to exfiltrate some data potentially. This is where the exfiltration module comes handy.
It offers several features including:
- Extracting info from the AAD tenant of the user, using Graph API
- Extracting Teams chats, contacts and files through the Teams API
- Extract cookies and authentication tokens from an exfiltrated Teams database
- Extract files from OneDrive or SharePoint
- Extract mails using Outlook API
- Exfiltrate JWT tokens to abuse SSO mechanism
It also allows to exfiltrate directly from a token as an input or abuse the refresh tokens from the victim’s cookies.
Method 1: AAD
Let’s have a deeper look and test the aad exfiltration:
We see here it lists the accounts where the spraying was successful. We know John Smith did not have MFA, so let’s target him. Interesting to note here that Fireprox is not used as the purpose is to exfiltrate data. Probably future iterations of TeamFiltration would allow to exfiltrate to a third-party server:
We can see this allowed us to grap all the users of the tenant, for spraying or phishing attempts:
Method 2: All
Let’s use the exfiltrate all method now. Before trying John Smith, where we know no MFA is enabled, let’s also use Slit, were we have the password and maybe some CA policies are not set properly and MFA will not be required for all APIs?
No luck this time! but remember this is a good way to try and bypass CA policies if, MFA is not mandated in all cases.
Now with John Smith: we can see that next to the users in the same tenant like before, we exfiltrated a lot of interesting information, including Teams chats, emails, OneDrive documents, contacts…:
We see in the exfiltration that several APIs will be targeted, so we can directly take a look at Non-Interractive Sign-in Logs and see some interesting details, like the IP or yet the applications used:
That’s pretty much all you can see in the logs, no expected UAL or other audit logs involved here.
Now, for the other attempt, with exfiltrate –all: let’s first look at what was detected for Slit:
Nothing in non-interractive sign-in logs? Why is that? because the CA policy kicked in and asked additional factors, hence triggering interraction with the user:
If we look at John Smith now, we can see all the non-interractive sign-ins and the applications used:
In this specific case however, it is not only about AAD and we triggered as well some interesting audit logs, in UAL (or OfficeActivity in Microsoft Sentinel):
As a bonus, TeamFiltration proposes a ‘backdoor’ module. The purpose of this module is basically to interract with the OneDrive API using a custom CLI. Why would you do that? Because that way you can replace a legitimate file in the user’s OneDrive, by the same file with a malicious macro or a HTA file embedded. You can look at dates to see how often the user is using some files, to grow chances of the payload to be executed. Another nice trick mentionned in the Defcon talk is that the author realized the Desktop folder is often sync’d to OneDrive by default. The desktop folder is often a set of shortcuts (LNK files). You can therefore simply replace one of the shortcut to point to a malicious payload or an executable you’d like to launch with user’s privileges.
The backdoor will generate sign-in events, like the exfiltration module. Interestingly showing up as Teams app (due to the OAuth ‘trick’ explained in the talk):
But we can, from UAL again, see also the file being recycled and uploaded:
You notice this time Fireprox can be used as there is no exfiltration. Of course UAL triggers a tons of logs in a real environment and detections based solely on this would lead to tons of false-positives. It can however be interesting as part of a hunt, or in cross-detections scenario with spraying attempts.
Preventing / detection
There are several measures you can take to detect, prevent or partially mitigate enumueration, spraying and exfiltration risks. Here are a few things to get you started:
- MFA with Conditional Access
- Enable Unified Audit Log (UAL)
- CA policies - Have strong CA policies in place
- Password Spray protection
- Password complexity and Azure AD password protection
- Azure AD Identity Protection: use CA policies and not risk/sign-in profiles in AAD IP directly
- Limit cross-tenant search in Teams
- Be sure you understand Seamless SSO and how to configure it properly
- TeamFiltration allows you to input tokens or steal them and re-use, so a fresh new feature of Azure AD which did not make enough noise according to me, will help prevent tokens theft
I did not build specfic detection queries for this tool yet, but generally you want to rely on generic spraying / exfiltration detection rules, as the framework used does not matter too much at first. If you look in the official Microsoft Sentinel repository, you will find some interesting detection rules around Azure AD in general.
Great talk and work from @flangvik here (except for the missing ‘s’ in the name of the tool which I had a hard time with :D) and I am sure we will see great improvements of the framework over time for purple teams and red teams to play with. Of course, we can also expect adversary usage in the wild like for AADInternals or PowerZure.Back to Top ↑