MFA Slipstream - Phishing MFA PoC Walkthrough

MFA Slipstream PoC

MFA Slipstream is a Proof of Concept (PoC) I put together a few months ago while on a phishing engagement. It uses JavaScript and a Python back-end to collect a target's username and password, as well as multi-factor authentication token.

I say PoC and not tool, because there are some obvious features missing from the PoC's current state, like: the ability to clone a site and stitch in the necessary JavaScript (this is done by hand), or the ability to scale better by some type of user session/state.

At its core the MFA Slipstream tool is a Bottle.py web application that controls a web browser via Selenium. JavaScript on the phishing pages communicates data back to the tool (via XHR), and the tool reacts based on the input from the phishing pages. This bidirectional communication between the MFA Slipstream tool and the JavaScript on the phishing pages allows for a very realistic, and real-time experience for the target.

For a demo and attack explanation in the form of a short video, check out my previous post here: Phishing Your Way Past Multi-Factor Authentication

If you're just looking for the code, you can find it here: GitHub - MFA Slipstream

Assumptions

This code walkthrough assumes the following:

  • You are familiar with Social Engineering and running a phishing campaign
  • You know how to configure DNS, which record types to choose, etc.
  • You know some Python and JavaScript

Attack Platform Overview

The attack really isn't that much different than a normal phishing attack, other than it uses some nifty tooling to do everything in real-time.

We have 3 components to our attack setup:

  1. Attacker machine - mfa_slipstream.py - This is where the mfa_slipstream.py tool runs. It handles collecting username and password data, it facilitates the transition from the username and password to the collection of MFA tokens, as well as handles collecting the MFA tokens themselves.
  2. Phishing Stage 1 - collect_userpass.js - Phishing Stage 1 is pretty much a standard phishing page. What is special is the JavaScript used to communicate back with the attacker machine. Clone the target's page, and use this JavaScript to facilitate communication with the attacker machine. After collecting the username and password we move on to Stage 2.
  3. Phishing Stage 2 - collect_mfa.js - This bit is a bit more complicated. This contains 2 pieces of JavaScript. Part 1 handles sending the MFA code back to your attacker machine, part 2 parses information from the UEL set by collect_userpass.js to display the correct authentication type and details to the user.

An overview of how the components interact with each-other is illustrated in the following diagram:


Attack Diagram

Below is a more detailed explanation of each of the 3 components.

Attacker Machine

The attacker machine is where mfa_slipstream.py is running, waiting for input to drive it from the Phishing Stages 1 and 2 JavaScript.

I tried to keep the code self documenting and well commented, so I won't spend too much time here. What I want to do here is break down the different components within, and give a high level view of what they do. Links below are to the locations in the code on GitHub within the tool mfa_slipstream.py.

MFA Slipstream Stage 0 - This is the tool setup stage. It doesn't interact with the phishing pages, it just gets the attacker's browser ready for action. This stage is a single method, go_to_signin(). It is called at the bottom of mfa_slipstream.py as part of launching Firefox and navigating to the phishing landing page. The protected page is visited by the attacker's browser, then a test email account is entered into the login form, which triggers any secondary redirect to the target's page.

O365 often does a double redirect. After entering an email address matching the target's domain, you are sometimes redirected a second time to the actual login page. STAGE 0 takes care of getting the attacker's machine onto the correct and final login page.

MFA Slipstream Stage 1 - This stage accepts the username and password from the Phishing Stage 1. dossologin() parses out the username and password, passing them to do_usernamepass_login() to do the login. do_usernamepass_login() does the login and collects the authentication type. It also collects the last 2 digits of the target's phone number where relevant, so it can return it to the calling function. It does this using Microsoft's already existing constants declared on the login page, then returns the authentication type to dossologin().

MFA Slipstream Stage 2 - This stage does one of two things, depending on the authentication type being used. If the authentication type calls for a one-time password to be entered, domfa() is used to collect that information and pass it to do_mfa_code_entry(), which uses Selenium to enter the MFA code and take a screenshot of the authenticated page.

If the authentication type is just an "approve" prompt, checkload() is used by the phishing page to check whether or not the target has clicked "approve" on their app notification. Once the target has approved the login, an appropriate response is returned to the calling phishing page.

Configuration, Utility Functions and Execution - There is a bunch of other stuff in the PoC mfa_slipstream.py script, including:

  • quicksave() - This takes a screenshot and saves the current session cookies to a file on disk for later use.
  • enable_cors() - Adds CORS support, for when your attacker box is on a different domain.
  • class SSLWSGIRefServer() - Used for adding SSL/TLS support to the app server.

Also some utility functions that are documented enough to be self explanitory.

The LET IT RIP section just prints the application banner, launches the Selenium controlled Firefox browser, and starts the Bottly.py server.

Phishing Stage 1 - JavaScript and Username/Password Collection

Everything for Phishing Stage 1 is contained in collect_userpass.js. It is a single chunk of JavaScript.

It uses document.getElementById to collect up the username and password the user entered and encodes them for transfer. It then ships them back to the attacker machine at the /dossologin route (MFA Slipstream Stage 1).

From there the attacker machine logs in to the target page. Once logged in mfa_slipstream.py detects the authentication type the user is using (SMS, Voice, Authenticator App, etc.) using MFA Slipstream Stage 1. The attacker machine then sends that information, as well as the last 2 digits of the user's phone number (for display purposes) back to the phishing page.

At that point the JavaScript uses the authentication method and the last 2 digits of the user's phone number to request the Phishing Stage 2 page. By passing Phishing Stage 2 the proper authentication type information we can deliver a very believable phishing page to the target.

Note: I have not included the fully cloned pages with the JavaScript integrated for several reasons. First, lots of copyright notices in there from Microsoft. I want to keep the license on this tool strictly MIT licensed. Second, you really don't need it. Each target using MFA probably has a branded page so a page cloned from another organization isn't very useful. Also, dropping in the JavaScript to a cloned page is a good learning experience in getting familiar with how this all works.

Phishing Stage 2 - Collecting the MFA Token, More JavaScript

Everything for Phishing Stage 2 is contained in collect_mfa.js. It is two pieces of JavaScript.

Let's actually start with Part 2. It is executed first, but is labeled Part 2 because it is located further down the actual code of the phishing page. Part 2 is mostly a bunch of parsing. It takes in URL components, parses them into variables, then depending on the authentication type configures the page to display correctly.

One interesting piece of code is waitForMFASApprove(). First, think about the different types of authentication methods: we have SMS, we have voice, and we have a one-time password from the mobile app. All of these are driven by a user entering a code, and clicking the sign in button. The one odd authentication method is the notification type.

In practice, for the notification type, the service sends the user a notification and the user just clicks "Approve". There is no code entered. This is where waitForMFASApprove() comes in. When we run into that authentication type, we wait for the user to approve the login, which will log our attacker in, and only then do we allow the target to move on to the error page.

Part 1 is the function doMFAAuth(). This is pretty straightforward, and actually simpler than Part 2. All we need to do here is collect the token the user entered, send it back to the attacker so they are logged in using MFA Slipstream Stage 2 of mfa_slipstream.py, then dump them off to an error page using window.location.replace.

Deployment Tips and Tricks

  • NGINX - I used nginx as my reverse proxy, and can't recommend it enough. When you want to have a single server hosting your email server, phishing metrics collection, and phishing landing page, nginx is the way to go. Especially if you want to serve multiple domains, and have a quick and easy setup. You can then clone it to a new VM/AMI instance and have multiple campaigns going with minimal effort.

  • Multiple Gateways / Scaling - Unlike Evilginx, this solution is not built to scale well. The way this issue was overcome in a company-wide type Social Engineering engagement was to leverage the cloud. Create multiple gateways for users. Say, batch 1 is 10 users, they all will hit a unique landing page, which leads to 10 distinct attacker boxes and MFAS Stage 2 collection pages. It works, its just a little slow. In very targeted attacks against just a few users at a time, this wasn't a big issue.