Recently, I attended the Thrive Conference in Ljubljana, Slovenia. During the IT Pro “Ask the Experts”-panel, an interesting question was raised: “How do I protect an on-premises AD FS environment from password spray attacks?”. Why is this such an interesting (and relevant) question, you may wonder? Well, over the past couple of months a lot of my customers have been targets of (organized) attacks, and I have been helping them overcome the effects of such attacks. Unfortunately, my customers are not the only ones! There is a disturbing trend, showing the number of attacks is steadily increasing, worldwide. These attacks aren’t only targeted towards large enterprises, it’s organizations of all sizes; seemingly random even.

The short answer to this question is: there isn’t ONE thing you can do solve the issue. Instead, there’s a bunch of things you could (and probably should) do. Spoiler: enabling MFA is one of them!

Before we dive into the technicalities, let’s first look at the dynamics of such attack(s). In a password spray attack, attackers use common passwords across various services (applications) to try and gain (unauthorized) access to password-protected services/content. The passwords they use are typically the so-called ‘usual suspects’ and include passwords such as “Summer2018!”, “Winter2018”, “Passw0rd” or the more ‘complex’ variant thereof “P@$$sw0rd”. I know you just chuckled, and you know why!

Unfortunately, attackers do not stop there. As more and more databases with user credentials are breached (more recently, Marriott ‘lost’ data from approx. 300 million accounts –including login data), attackers use the username/password combinations from those breaches in a similar way to try and gain unauthorized access to resources.

Because, nowadays, a lot of services are published onto the Internet, it’s almost a child’s game to target public services and be quite successful with these types of attacks. Even with a success ration of less than 1%, targeting several thousands of public applications yields enough success to make it worth their while. They are so successful because humans are creatures of habit and tend to use the same password for more than one service…

Once the attackers are in, they will use the breached account to (try) and exploit other vulnerabilities and increase privileges within the environment. Ultimately, their goal might be to steal data, encrypt contents and ask for ransom, etc.

So, now that we have that out of the way, let’s take a closer look at what you can do to harden your environment. Mind you that these recommendations are targeted primarily to Office 365, but the same principles (can) apply to other services behind AD FS as well! Note the recommendations below appear in no particular order. I did, however, add some thoughts here and there on what I believe is super useful and easy to implement. What you can (or cannot) do is up to you to determine, and in function of what applications you use, what your end user experience must look like and the buy-in from your management. The latter sometimes is the biggest challenge!

Use cloud authentication

Before diving into what you can do to enhance your AD FS infrastructure, you can also choose to replace AD FS with something else. For example, you could move to cloud-based authentication and use Azure AD accounts to authenticate to Office 365, federate with other applications, or use the Azure AD App Proxy to access on-premises applications.

Moving away from AD FS to cloud authentication moves the responsibility from dealing with these types of attacks from your own environment to Microsoft; they have the collective brain power, resources (like compute power) to intelligently detect and stop these types of attack. Although it solves the problem for you, it merely shifts the responsibility from having to deal with it yourself away.

When you move your authentication to the cloud, you can leverage additional components such as Azure AD Conditional Access. This, in turn, allows you to more intelligently perform authentication based on a set of (adaptive) rules. For example, Conditional Access can be used to not require MFA when you are using a known device from a known (trusted) location like your company network. When someone then tries to authenticate from outside the corporate network, or they aren’t using a registered device, they must perform MFA which then makes it a LOT harder for e.g. an attacker to gain access to an account –even when they have the right username/password combination…

Along the same lines, Microsoft offers Identity Protection in Azure AD. Instead of, or in addition to, defining a set of rules that will determine how authentication should be performed (Conditional Access), Identity Protection will leverage extensive AI-models to calculate a risk-score for an authentication attempt. Based on that score (low-medium-high), you can specific actions, such as denying access or requiring MFA (to prevent locking out valid users).

Telling people to replace AD FS with cloud authentication is easy. Doing so isn’t always possible, certainly not in the short term. So, let’s explore some other options.

Enable Multi-Factor Authentication

If there is one thing you should consider, it’s enabling MFA. Microsoft shared some startling numbers at their annual Ignite Conference: enabling MFA reduces the success rate of such attacks by an astonishing 99%. At the same time, they also revealed that too little organizations take advantage of MFA…

While it is an efficient way to stop password (spray) attacks from being successful, it doesn’t solve the problem caused by using passwords, or inherently the insecure way in which they are used and chosen by users. Nonetheless, MFA will render a valid username/password-combination virtually useless without the additional factor –something which is typically much harder to breach.

At the very least, you should use MFA for all your privileged accounts. These accounts can do most harm to your environment and should be well protected.

Off-topic: I know some will refer to the recent Azure MFA outage and point out that when MFA is not working, it really creates an operational problem. While this is completely true, there are few alternatives. Not turning on MFA could be far worse and will require you to keep a very close eye on the use of that account…!

Alternatively, consider keeping one or more break-glass accounts. These can be admin accounts that can be exempted from MFA using e.g. Conditional Access (e.g. require that the account can only be used from the internal network, on known devices). Having such an account will allow you to perform emergency-changes in your tenant, should such an MFA issues occur again in the future.

Turn on MFA as primary authentication

In addition to just using MFA, you can explore configuring MFA as the primary authentication method in AD FS 2016 and 2019. This doesn’t mean you can’t use passwords anymore: it can be used as the second factor after the initial MFA was successful. It means that attackers would first have to successfully perform MFA before they could attempt the username/password combination; very effective!

Use “advanced”, but built-in, AD FS capabilities

AD FS in Windows Server 2016/2019 have some features that are extremely useful. The first one is “(Extranet) Smart Lockout”. This capability will look at (un)successful authentication attempts and use the information gathered to proactively block authentication attempts from specific locations (IP addresses). The feature works in a very similar way than Azure AD’s smart lockout and is a good starting point to protect your environment.

Another feature is the “Banned IP”-list. This is a list of IPs that you can configure on your AD FS servers for which authentication attempts will be ignored. This is similar to the Smart Lockout capability, but you’ll have to configure it manually. If you leverage Azure AD Connect Health for AD FS, you can use the Risky IP report to block the IP addresses that consistently seem to be the source of unsuccessful authentication attempts and haven’t (yet) been caught by e.g. Smart Lockout.

Prevent bad passwords from being created

At the source of the problem (of password spray attacks) is the use of simple and predictable passwords. Educating users on what a good password is one thing, but do good passwords even exist? Even more so, users are notorious for not always playing by the rules…

So, better than just (blindly) trusting them, you can leverage Azure AD’s password protection in your on-premises environment. Without going into too much technical details, this feature will ensure that whenever a password change occurs in the on-premises environment, an agent will verify if the new password does not match a known bad (or banned) password and – if it does – prevent the password change, forcing the user to choose another password. Although this doesn’t solve the problem from known passwords to be used, it will – over time – ensure less simple and too obvious passwords exists in your environment. In turn, this decreases your attack surface a little bit.

Go for password-less, or just less passwords

Discussing the topic of whether getting rid of passwords is a good thing or not is worth an article by itself. Tldr; replacing passwords with something else is a good idea 😊

Today, you can already start by leveraging certificate-based authentication. This, too, is harder to compromise than passwords, but also notoriously difficult to implement (because of the management overhead for the deployment of client-side certificates etc). Because of the complexity, it’s not always a desired solution.

There are a few ways to go passwordless. One way I found to be super easy, is to use the Microsoft Authenticator app. Combining the requirement for MFA with password-less and Conditional Access is very powerful and super easy to deploy. Unfortunately, this will only work considering that 1) users must have a smart phone with the Microsoft Authenticator, and 2) they must access resources with their Azure AD account… Soon, you’ll also be able to use hardware (OAUTH) tokens instead of the authenticator app, adding (a bit) more flexibility in your options!

If you are up-to-date with your workstations (that is Windows 10, folks!) and you are using modern applications like Office 365 Pro Plus, you are in a good starting position to start adopting password-less authentication by implementing Hello for Business. Password-less is still very much something for the future, and passwords won’t disappear overnight. However, now is a great time to start planning and testing! If not, at the very least use it for a small subset of your (IT) users so you can get acquainted with it, both from a deployment and manageability perspective…


In Dutch, we have a saying: “meten is weten”. This roughly translates into “measuring is knowing”. Sounds horrible, doesn’t it? What it means though – in the context of this article – is that if you don’t measure/monitor your AD FS environment, you won’t know what’s happening or that you are under attack!

Azure AD Connect Health for AD FS is a good way to gain visibility into what’s happening in AD FS. However: what if you don’t have the licenses for it? Although there’s plenty of other (monitoring) solutions that can help you get the right information from AD FS, below I’ve included a (manual) way of using PowerShell to detect failed authentication attempts. It’s by no means a replacement for a decent monitoring solution –but it could be a starting point!

The script uses the AD FS event log information to lookup the IP information from the (failed) authentication attempts and compiles a list of countries that are generating these unsuccessful events. To retrieve the country to which an IP address belongs, the scripts uses the REST service. You will have to get your own API keys to perform the lookups! In the end, you can use the information from this report to proactively block specific IP addresses from the Internet. You can do this on your firewall or using the Banned IPs-feature in AD FS.

First, we’ll get the relevant events from the event logs:

[code language=”powershell”]
#Define time frame for which to retreive events. Can be omitted.
$StartDate = "26/10/2018 19:00:00" | Get-Date
$EndDate = "27/10/2018 14:59:59" | Get-Date

#get events
$eventsFailed = Get-WinEvent -FilterHashtable @{logname=’Security’; ID=1203; StartTime=$StartDate; EndTime=$EndDate} -ComputerName
$eventsSuccess = Get-WinEvent -FilterHashtable @{logname=’Security’; ID=1202; StartTime=$StartDate; EndTime=$EndDate} -ComputerName

Next, we’re taking the events and extracting the required information from them. I am not a PowerShell-guru, so I am sure there are better ways to do this. However, below is what I could come up with in the short period I had to troubleshoot whilst an attack was going on. The customer I created this for then added a bunch of additional information (like coordinate and time stamps) afterwards.#reset variables

[code language=”powershell”]
#reset variables
$result = @()

#Loop through results and compile the list

foreach($event in $eventsFailed){
#workaround to get information.
[xml]$eventXML = [xml]$Event.ToXml()
$eventXML.Event.EventData.Data[1] | Out-File xml2.xml
[xml]$xmlData = Get-Content .\xml2.xml
$data = New-Object PSObject
$ipAddress = $xmlData.AuditBase.ContextComponents.Component[3].IPAddress.Split(",")[0]

#the clause below differentiates between internal and external IPs
if ($ipAddress -eq "<internal IP addresses>")
$data | Add-Member -MemberType NoteProperty -Name UserID -Value ($xmlData.AuditBase.ContextComponents.Component[0] | Select UserID).UserID
$data | Add-Member -MemberType NoteProperty -Name IPAddress -Value $ipAddress
$data | Add-Member -MemberType NoteProperty -Name Continent -Value "Europe"
$data | Add-Member -MemberType NoteProperty -Name Country -Value "Belgium"
$data | Add-Member -MemberType NoteProperty -Name City -Value "City"
$data | Add-Member -MemberType NoteProperty -Name Region -Value "Region/Province"
$data | Add-Member -MemberType NoteProperty -Name Lat -Value "50.9160" #latitue
$data | Add-Member -MemberType NoteProperty -Name Long -Value "4.0402" #longitude
$data | Add-Member -MemberType NoteProperty -Name Status -Value "Failure"
$data | Add-Member -MemberType NoteProperty -Name TimeCreated -Value $event.TimeCreated
$data | Add-Member -MemberType NoteProperty -Name Source -Value "Internal"
$data | Add-Member -MemberType NoteProperty -Name Date -Value $event.TimeCreated.ToShortDateString()
$data | Add-Member -MemberType NoteProperty -Name DateNearestHour -Value (($event.TimeCreated).AddMinutes(-(($event.TimeCreated).Minute % 60))).ToString("yyyy-MM-dd HH:mm")
$data | Add-Member -MemberType NoteProperty -Name TimeNearestHour -Value (($event.TimeCreated).AddMinutes(-(($event.TimeCreated).Minute % 60))).ToString("HH:mm")
#perform REST lookup to get IP information
$ipLookup = Invoke-RestMethod -Method Get -Uri "$($ipAddress)?access_key=<accessKey>"

$data | Add-Member -MemberType NoteProperty -Name UserID -Value ($xmlData.AuditBase.ContextComponents.Component[0] | Select UserID).UserID
$data | Add-Member -MemberType NoteProperty -Name IPAddress -Value $ipAddress
$data | Add-Member -MemberType NoteProperty -Name Continent -Value $ipLookup.continent_name
$data | Add-Member -MemberType NoteProperty -Name Country -Value $ipLookup.country_name
$data | Add-Member -MemberType NoteProperty -Name City -Value $
$data | Add-Member -MemberType NoteProperty -Name Region -Value $ipLookup.region_name
$data | Add-Member -MemberType NoteProperty -Name Lat -Value $ipLookup.latitude
$data | Add-Member -MemberType NoteProperty -Name Long -Value $ipLookup.longitude
$data | Add-Member -MemberType NoteProperty -Name Status -Value "Failure"
$data | Add-Member -MemberType NoteProperty -Name TimeCreated -Value $event.TimeCreated
$data | Add-Member -MemberType NoteProperty -Name Source -Value "External"
$data | Add-Member -MemberType NoteProperty -Name Date -Value $event.TimeCreated.ToShortDateString()
$data | Add-Member -MemberType NoteProperty -Name DateNearestHour -Value (($event.TimeCreated).AddMinutes(-(($event.TimeCreated).Minute % 60))).ToString("yyyy-MM-dd HH:mm")
$data | Add-Member -MemberType NoteProperty -Name TimeNearestHour -Value (($event.TimeCreated).AddMinutes(-(($event.TimeCreated).Minute % 60))).ToString("HH:mm")


$result += $data

#get the results. This can be changed to output to CSV, XML or anything else, really.
Write-Output $result

Note: you’ll have to turn on AD FS Audit Logs for these events to start popping up in your Security Logs!

Help! I’m under attack…!

If your monitoring shows an unusual high number of failed authentication attempts, you might be under attack. But what to do next? First, keep calm and breathe! Now is not a good time to panic. Without you, the attackers have free reign! Pulling the plug might work but it is a bit drastic. So, let’s hold off on doing that for now.

Once you’ve overcome the initial shock, try to identify what the attack vector/angle of attack is. Perhaps the suspicious activity is coming from just a few IP addresses? Good. Block those. If things take a turn for the worse, you might have to (temporarily) block external access to resources. This doesn’t necessarily mean you have to lock out your users: if they have other means to access the (internal) network such as through a VPN-connection, they would still be able to authenticate directly to the internal AD FS servers.

The challenge with the VPN-approach is that it doesn’t work for basic authentication (e.g. ActiveSync with Exchange Online). This is because with basic authentication, the authentication request(s) are initiated by Exchange Online and blocking those IP addresses will cripple access for everyone who uses basic authentication.

As such, it’s important that you try to move away from Basic Authentication as a rule of thumb anyway. In a modern world, and with modern applications, there should be no need to basic authentication. For example, Outlook and Outlook Mobile can perfectly live without it. POP3 and IMAP on the other hand, not so much…

Basic Authentication is the root of all evil. A simple username/password combination is no longer from this era. It might have been a fitting solution in the early days of computing, but that’s long past. Even complex and long passwords provide little to no value if used incorrectly, which is how most of the users use them. It’s not just because of what passwords one use, passwords themselves are because of how they are used also subject to man-in-the-middle attacks, etc… But that’s besides the point of this article. What is the point, is that you should disable basic auth when you get the chance, and there’s plenty of ways you can do it in Office 365/Azure AD:

  • Use Conditional Access to block legacy Auth
  • Use Authentication Policies in Exchange Online to block basic auth
  • Block specific protocols (such as e.g. POP3/IMAP) for specific user accounts.
  • Use AD FS Claim Rules to (selectively) block basic auth from specific locations (or users, or groups, etc.)

If you can’t block basic auth completely, limiting what accounts can use it, is already a good first step! Remember, there is no single security strategy that you can implement in a single go. As with many things, it’s something that takes time to setup correctly and get users accustomed to. It’s like a good wine: it can get better with the age. But if you wait too long, it can become bad as well!

Wrapping up

There are many countermeasures, each addressing parts of the challenge. The more of the above features and capabilities you implement or configure, the more secure your environment becomes. However, it doesn’t mean that doing all of this will make you 100% secure, as there is no such thing…

Instead, make sure that you take a holistic approach. After all, your defense is only as good as the weakest link in the chain. Having a multi-layered approach is key. Secondly, make sure to monitor your authentication traffic. If not, you’ll be flying blind and it might take you a while to figure out something bad is happening.

Over the next couple of weeks, I’ll be diving more deeply into some of the topics mentioned in this article. Until then, all there is left for me is to wish you a great end of the year!