From Error to Entry: Cracking the Code of Password-Spraying Tools
Introduction
First things first, all of the tools in this blog post are really great tools and I have used most of them. (Thanks to the authors of the tools to sacrifice time and energy to write tools for the community!). After reading this post, I hope everyone will start to read the code in the tools more and do their own due diligence when it comes to what you are trying to achieve. By following that principal, you will learn more and understand your attacks way better. This post highlights a neat little thing I found on a recent engagement with W9HAX that gave us initial access into an environment.
Error Code Our Way In
We have all been there, we want to gain access into the customers network and we need to do some password spraying in order to achieve it. So, how do we go about password-spraying and getting access? On my engagements, Office 365 is normally involved and there are several tools out there that can password-spray Office 365.
Let me list out the most popular ones (I also asked Twitter for help on the subject):
- MSOLSpray and MSOLSpray.py
- CredMaster
- o365spray
- Go365
- TREVORSpray
- o365enum
- SprayCharles
- Oh365UserFinder
- TeamFiltration
All of these tools are great and do a fantastic job of spraying passwords and showing success or not in the output. They have other features as well, but I am not going to go over each tool since that is not the purpose of this blog post. I cannot be 100% certain on this, but based on the code from the different projects, it might seem as though MSOLSpray was an inspiration to most of the tools in terms of the spraying. It might seem that MSOLSpray was one of the first tools out there in terms of spraying towards Office 365 (This is just my guess). Okay, here is how MSOLSpray determines if it is a success or failure.
Basically, this means that when a request with the current username and password is made towards https://login.microsoft.com, it will mark it as a successful login if the StatusCode returned is 200. If it is not, it enters the Else Clause, and this is where things get interesting.
As you can see, it looks up the error code from the response inside the Else Clause, and these error codes are part of the Microsoft Entra (previously Azure AD) Security Token Service (Shorted as AADSTS). As time of writing this blog post, there are 302 different error codes you could get as a response from AADSTS. In November 2020, there were only 242 different error codes, so things are changing. Our big discovery during our engagement was that the error message AADSTS50079 means that you can onboard MFA for the user. In most tools, this error code is just highlighted as MFA is in use. Here is the code from MSOLSpray specifically:
This might be information you already knew, or this is something people usually check during spraying. However, let's assume for now that most consultants will maybe randomly try to login one or two accounts with MFA in use and see that MFA is required and move on. That’s what I would have normally done before. I never took the time to check all or even check if there was a difference between AADSTS50079 or AADSTS50076. Well, there is a big difference. AADSTS50079 does indeed mean that the user needs to onboard MFA. The screenshot below is what you will see with the AADSTS50079 error.
Long story short about our engagement, we were able to login to Office 365, complete the registration of MFA for the user, and gain remote access.
This entire adventure got me thinking that this is possibly something other offensive security people are not aware of in their testing. Was there more we are missing and not checking with regards to those error messages and StatusCodes? Are there any differences in the tools we use regarding those error messages?
Comparing AADSTS Errors and Code
If we compare the AADSTS documentation from 2020 and now, it is no wonder why code was initially written to just highlight it as MFA enabled and not that the user must onboard MFA.
The screenshot above shows the description from the 2020 version that I pulled from the wayback machine. As you can see, it does not say that the user has to onboard MFA, it says that the user is required to use MFA. Now, let’s check the 2024 description of that same error message.
You can see that this actually means that the user needs to complete the registration of MFA. Also, if you look at the name, UserStrongAuthEnrollmentRequired, we are also getting a hint with enrollment being required. We also found that it might be possible that the error AADSTS50072 and AADSTS53004 would also reveal accounts that are required to complete the MFA setup; However, we did not experience that on our engagement (We did look for it, though).
Just for fun, I decided to go through some of the tools to see the error codes they are using. First let’s look at MSOL Spray and MSOLSpray.py:
AADSTS CODE | Description in code |
AADSTS50126 | Standard invalid password |
AADSTS50128 | Invalid Tenant Response |
AADSTS50059 | Invalid Tenant Response |
AADSTS50034 | Invalid Username |
AADSTS50079 | Microsoft MFA in use response |
AADSTS50076 | Microsoft MFA in use response |
AADSTS50158 | Conditional Access response |
AADSTS50053 | Locked out account or Smart Lockout in place |
AADSTS50057 | Disabled account |
AADSTS50055 | User password is expired |
All of these error messages make sense and are the most likely to hit, in my opinion (except the AADSTS50079 that has changed, of course). Let’s look at some of the other tools and what error codes they choose.
CredMaster has the following:
AADSTS CODE | Description in code |
AADSTS50126 | Standard invalid password |
AADSTS50128 | Invalid Tenant Response |
AADSTS50059 | Invalid Tenant Response |
AADSTS50034 | Invalid Username |
AADSTS50079 | Microsoft MFA in use response |
AADSTS50076 | Microsoft MFA in use response |
AADSTS50158 | Conditional Access response |
AADSTS53003 | Conditional Access response |
AADSTS530034 | Conditional Access response |
AADSTS50053 | Locked out account or Smart Lockout in place |
AADSTS50057 | Disabled account |
AADSTS50055 | User password is expired |
As you can see, two additional error messages were added to Credmaster.
Finally, let’s do o365spray.
AADSTS CODE | Description in code |
AADSTS50126 | Standard invalid password |
AADSTS50128 | Invalid Tenant Response |
AADSTS50059 | Invalid Tenant Response |
AADSTS50034 | Invalid Username |
AADSTS50079 | Microsoft MFA in use response |
AADSTS50076 | Microsoft MFA in use response |
AADSTS50158 | Conditional Access response |
AADSTS50053 | Locked out account or Smart Lockout in place |
AADSTS50057 | Disabled account |
AADSTS50055 | User password is expired |
AADSTS500011 | Invalid resource name |
AADSTS700016 | Invalid application client ID |
AADSTS53003 | Conditional Access response |
Compared to MSOLSpray, three additional error codes are present in o365spray.
As you can see, many of tools do the exact same things in terms of the MFA. However, there are some interesting exceptions. For instance, Oh365UserFinder highlights that MFA is not configured correctly for the AADSTS50079 error.
Go365 has a check for AADSTS53004.
TeamFiltration got the AADSTS50079 error feedback correct as you can see in the image below.
Tool Overview
Here is a table of the number of error codes that have been implemented in the various tools I checked. Fortunately, most of the tools' output unknown errors and the error codes if it is an unknown error; However, it is up to you as an operator to look them up and make sense of them. Remember that there are actually 302 different AADSTS error codes as of writing this blog post.
Tool | Error codes implemented |
MSOLSpray and MSOLSpray.py | 10 |
CredMaster | 12 |
o365spray | 13 |
Go365 | 10 |
TrevorSpray | 15 |
o365enum | 10 |
SprayCharles | 10 |
Oh365UserFinder | 11 |
TeamFiltration | 11 |
Outro and Takeaways
My thought behind this post was to highlight that we, as users of other’s tools, need to spend some time to understand what is going on to fully understand everything. It is not always the case that the author of the tools was able to think of all scenarios or even had the correct documentation at hand when writing the tools. This post also shows that we often copy code from each other when writing new tools (I know I do) and sometimes it might be worthwhile to actually do some of the exploring yourself instead of assuming the code you copy handles everything. In this case, most of the tools did not highlight that you could actually onboard MFA, which could lead to access in most cases.
Since I am all for contributing to the community, I went ahead and made a pull request to each tool to fix the issue I found.
CredMaster: https://github.com/knavesec/CredMaster/pull/75
Go365: https://github.com/optiv/Go365/pull/15
MSOLSpray: https://github.com/dafthack/MSOLSpray/pull/13
MSOLSpray.py: https://github.com/MartinIngesen/MSOLSpray/pull/7
O365spray: https://github.com/0xZDH/o365spray/pull/27
Spraycharles: https://github.com/Tw1sm/spraycharles/pull/24
TREVORspray: https://github.com/blacklanternsecurity/TREVORspray/pull/38
One last interesting thing for reference is that you can go to Microsoft Online to look for unknown errors. This site gives you up-to-date error message description and information and this is very useful when you want to investigate those error messages.
As always, hope you found this post useful and feel free to provide feedback.