Note from the author: While writing this blog post I ran into a client where I needed to spray against the Office365 portal hosted by Microsoft directly and wrote some code to do so. If you just want to read that bit, skip to the end.
One of the things that I frequently run into while performing external penetration tests and web application penetration tests is a weird login page. These pages usually have a lot of weird redirection going on because they’re trying to play nice with Active Directory and other services, so sometimes you’ll have to enter credentials multiple times on multiple web pages just to authenticate to one service.
Very quickly: If you’re not sure what password spraying is, it’s when you take a list of users and you attempt a few passwords against all of the accounts. Often 1-2 passwords an hour to avoid locking everyone out of their accounts and getting an angry call.
When doing spraying and enumeration, we need to know how to format the request and how to tell if we were successful. The latter part is easy as every website gives you some variation of a failed login message in angry red text. In the case of enumeration, you’ll often have a message that leaks info, such as “User account does not exist” vs. “Incorrect Password”. In either case, constructing the request can be difficult when there’s a lot of redirection or weird things going on. We may have succeeded in the first login, but get redirected to a portal that has different creds. Of course, we care about creds that are good in one part, but I want the juicy creds that are going to get me all the way through to that inbox or VPN.
I was trying to log in to an Outlook Web App portal, but after I’d authenticate, I’d get redirected to a SecureAuth portal. After authenticating through SecureAuth, I’d get sent to an O365 login! After entering my creds three times, I’d finally have access to an inbox. I had obtained around 50 emails from OSINT, and I wasn’t about to go through all those usernames myself. Normally, for password spraying, I’ll either use MailSniper (which is awesome) or Burp (also awesome). In this case neither of these were working because of the login redirects or because I was just using them wrong. In either case, I have a piece of paper that says I know how to write code good, so we can figure this out. It’s shockingly easy, given the right tool.
Quick note: I also used this to bypass SecureAuth MFA on the same engagement, as some accounts had a “PIN Setup” option. To do this I instrumented the same code to sign in, check for a PIN option, and spray weak pins. You have to actually click the keypad to do this and Selenium makes that easy, as we’re about to see, but it got me into Citrix Xenapp (twice!) for a total of 20 minutes worth of writing the code and punching go on a few PINs. That is all to say that browser automation is useful for more than just login pages. Anyways, let’s get to it.
If I have the choice of language, I’ll always choose Python. It has libraries for anything you need, the syntax is easy to pick up, and most importantly to me you can just get in there and start prototyping. Since we’re writing a custom sprayer for a home-rolled setup, we’re probably only going to use it once or twice. As such, there’s no need to do a bunch of software design or use “good coding practices”, we just want a quick and dirty script that gives us those good creds. Since Python has so many awesome libraries, we’re going to use the browser automation tool Selenium. You can download it for either Python 2 or 3, but Python 2 has reached end of life so we should probably get used to writing in Python 3. Again, we’re turbo prototyping, so do what works.
You can get Selenium using pip by running “pip install selenium”. If you’re having Python versioning issues because you have 2 and 3 installed, try “python3 -m pip install selenium” and that may help. You’ll also need to download the driver for the browser you’d like to use. I use the Firefox driver, which you can find here: https://github.com/mozilla/geckodriver/releases. You’ll also need to have Firefox installed. The same applies for Chrome, Opera, etc.
Here is my completed code for the example I gave above.
If you look through the code, you’ll see that we’re just doing the same thing three times in a row, with the elements tweaked. We start with a for loop and create our driver object, and tell it to get our mail page. In this case, we don’t need to wait since the code won’t run until the page is loaded. If you look at the source code of an OWA login portal, you can inspect element on the username/password fields and grab their IDs, conveniently just a username/password here. The send_keys method will emulate typing in whatever our text is, and Keys.RETURN will act as though you’ve pressed the ENTER key. Then we need to wait for a few seconds to let the page load, and we can check for our “known bad” string. You can get this string from just popping in a bad login and copy/pasting the angry red text. If we get that, we failed; otherwise, we’re successful! You can play with the delay time here, but if it’s too short, you’re going to get a lot of false positives. The page won’t be loaded yet so the failure string won’t appear.
Note: if we failed then we need to call driver.close(), otherwise the process will keep running and you’ll end up with a wicked memory leak (browsers aren’t cheap!). We have to call it before we call “continue” which, if you’re not familiar with Python, is a bit of a misnomer keyword. Continue tells the program to skip this iteration of the (nested) for loop, so we’ll skip to our next password. This is different from “break”, which will skip all future loop iterations, and “pass” which does nothing.
We could stop at this point since we’ve validated our creds, but what if we want to perform an automated action after we’ve logged in? So let’s go through all our hops. We do the exact same thing as before and inspect element on the username/password fields. I don’t know if every SecureAuth login page is like this. Even if it’s different, changing it is easy. On some pages the ENTER key doesn’t submit the page, so instead we can select the Submit button. Here, the button does not have a name, so we have to grab it via ID. Selenium offers a few other ways to grab elements if they’re lacking an id/name, you can find those in the documentation. Finally, to click the button, we just use the click method!
Then, do the same thing as before: wait, check for our bad string. Otherwise, we go to the next login page and do the same process as before to enter credentials and check.
At this point, you can use your imagination. You got into a user’s Outlook inbox and want to search a keyword and grab the results? Inspect element, find the search bar, and punch in those keywords. You can also apply this exact same process to user enumeration. Simply punch in the username to whatever field and look for the strings that let you do enumeration.
As I mentioned at the beginning, right before this post was going to be submitted I ran into a client where they had everything behind Citrix. First I tried spraying Citrix, but they had a bunch of subsidiaries who might not even be using that portal and I had found a million different username formats between them all. However, I had around 160 emails from OSINT and I wasn’t going to let them go to waste.
This is the code I came up with to verify the emails.
This is pretty much the same thing as earlier, with one key difference. I tried doing this by checking for the error text in the page source, but this gave me entirely bad results. So instead we can use a try/catch block to make the logic work. Since the element won’t be on the page if we get a valid user, Selenium won’t be able to find that element and will throw an exception. To handle that exception we’ll just print our valid user. And then using the finally keyword, which will run regardless of whether the try/catch block succeeds or fails, we’ll close the driver.
Now to accomplish the actual spraying:
So to address the elephant in the room with the nested try/except blocks… Yeah. This was the best way I came up with to do it at the time, I’m sure there’s a better way but we’re rapid prototyping and it works. If I ever write an actual usable project with this then I’ll address it. The issue that requires this is that if a user has two accounts for personal and work at the same address, the page will ask the user which account they want to use. I tried finding an element to search for but that just wasn’t working. So my solution was that if we fail to load the password field (i0118) then we’re probably on that double account page, so throw an error. I also added an extra half second sleep in order to avoid getting false positives if the page loads slow.
Some other considerations:
- By default, Selenium will launch with a GUI. This is great for initial debugging, but once you have a finished script launching, a bunch of visible browsers will just eat up more resources. You can launch it in headless mode by adding “firefox_options=’headless’” when you create the initial driver object (the 5th line of code).
- Selenium can be used to simulate human activity. While your target is still going to see 50 login attempts from users, it won’t just be 50 POST requests. The browser had to get the page first, then it moved around a bit. You can add more activity to Selenium like scrolling and a timing jitter if you want to expand on this.
- This process is linear, so it’ll go one at a time through each user and password. This can be good in some cases, as you likely need to wait an hour for the lockout policy anyways. If you want it to go faster you can use Python’s threading library, but keep in mind that this only helps in certain cases. Since we spend a lot of time waiting for network resources to load, it should help here.