Phishing Microsoft cloud users using malicious apps and OAuth2

Weeks ago, I read a blog post by Cofense showing how bad guys can trick users into granting permissions to a malicious application to “grab all the victims’ email and access cloud hosted documents containing sensitive or confidential information“. This kind of phishing attack uses the power of OAuth2 to bypass the need of user’s credentials and second factor.

During Covid-19 lockdown lot of organizations went remote using cloud services; Microsoft 365 services are widely used and a perfect target for attackers. Because of Cofense worrying statement I decided to better understand how these attacks work, how to detect it and what information an attacker can really steal from a corporate account or a personal one.

Before moving on I suggest reading OAuth 2.0 and OpenID protocols and application concept pages on Microsoft official documentation. To replicate Cofense attack scenario I

  • registered a malicious application on Azure AD
  • built a phishing portal where users are redirected after granting permissions to the malicious application
  • assume that the target user grants requested permissions to the malicious application after clicking a link in an appealing phishing email
Attack scenario

Let’s start building the phishing web site @ https[:]//phish.target.domain where the user is redirected after he grants the consent to the malicious app.

Microsoft has a public repository with a sample python app that signs-in users with the Microsoft identity platform and calls the Microsoft Graph, a unified API endpoint for accessing data across Microsoft 365 services (Office 365, Enterprise Mobility, Security and Windows services).

Using Graph endpoints and the user authentication token, the malicious app can access data across all these services based on the requested permissions.

I forked MS repo, made some customization to the web interface and added a python script to steal user access token and query Graph endpoints to download users/organizations data. In the README you can find detailed instructions to register and configure the malicious application (I called it cs-phish) on your Azure Active Directory tenant.

cs-phish app registration

Set the redirect URI to the target phishing domain https[:]//phish.target.domain/getAToken (where users are redirected and the magic happens), the usage options to any organization and personal account :evilface: and save CLIENT_SECRET and CLIENT_ID; will be used to configure the app later (file app_config.py).

cs-phish overview on Azure AD

You can run my slightly modified phishing portal using docker

# host
mkdir -p /opt/phish
cd /opt/phish
git clone https://github.com/gmellini/ms-identity-python-webapp.git

docker run -ti --name PHISH-OAUTH -p7777:7777 -v /opt/phish:/opt/phish ubuntu:18.04 /bin/bash

# in docker PHISH-OAUTH container
apt update && apt install python3-pip git vim
cd /opt/phish/ms-identity-python-webapp
pip3 install -r requirements.txt
# edit config file, set CLIENT_SECRET | CLIENT_ID
vim app_config.py
[...]
# start the portal
export LC_ALL=C.UTF-8; export LANG=C.UTF-8
flask run --host 0.0.0.0 --port 7777 &
# listen for incoming sessions
python3 monitor.py
[...]

Remember to set CLIENT_SECRET and CLIENT_ID in app_config.py

After the execution, the flask web application listens to port tcp/7777; the same port is published to the host and a reverse proxy must be configured to redirect HTTPS traffic (our target redirect URI) to local port tcp/7777.

This is the nginx configuration for https[:]//phish.target.domain virtual host; you can use let’s encrypt to issue certificates for your domain.

# Listen
server {
	listen 80;
	server_name phish.target.domain;
        location / {
                return 301 https://phish.target.domain$request_uri;
        }
}

server {
        listen 443;
        server_name phish.target.domain;
        # SSL/TLS configuration
        ssl_certificate    /path/to/fullchain.pem;
        ssl_certificate_key /path/to/privkey.pem;
[...]
        keepalive_timeout 5;
        # Flask app
        location / {
                proxy_pass http://127.0.0.1:7777;
		proxy_redirect     off;
		proxy_set_header   Host                 $host;
		proxy_set_header   X-Real-IP            $remote_addr;
		proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
		proxy_set_header   X-Forwarded-Proto    $scheme;
        }
}

At the same time monitor.py script listen for new session files stored in flask_session/ local dir and – when a new user lands on the portal – the script

I show two tests to understand how things work, using both corporate and personal accounts and setting different permission levels (the SCOPE var in config_app.py)

  • setting SCOPE var to [“User.ReadBasic.All”]: this “allow the app to read a basic set of profile properties of other users in your organization on behalf of the signed-in user
  • setting SCOPE var to the permission values of Cofense post [“Contacts.Read”, “User.Read”, “Mail.Read”, “Notes.Read.All”, “MailboxSettings.ReadWrite”, “Files.ReadWrite.All”]: this set of permissions it’s scary and its behavior depends on the user account type (corporate or personal)

This is the list of cs-phish app permissions on my tenant, consistent with the SCOPE I test.

cs-phish configured permissions

The phishing portal looks like this; by default, the malicious app SCOPE is set to User.ReadBasic.All

The Sign In link – the one I use in the phishing email – points to the legitimate Microsoft Office 365 login page; notice redirect_uri and scope parameters that match what we defined before.

Once a user clicks the phishing link – think that most of the users are already authenticated to the MS services just because they use it every day – just an authorization for the app cs-phish is requested. Landing pages are different for corporate (login.microsoftonline.com) and personal (account.live.com) accounts.

cs-phish: request for authorization

After consent is granted the user is redirected to the phishing portal and his token used to collect data (monitor.py script).

You can see the script in action in the video below, taken while phishing a corporate account. When you do the same on a personal one (outlook.com / live.com etc.) you don’t have any data back because there is no organization to hack 😛

monitor.py script just download Display Name and Email of the first 100 organization users; you can get the full list just iterating the query (hint: check odata.nextLink in the Graph query results).

Coming back to Cofense post is quite clear that is easy to phish users and collect users/organizations personal data (Name, Surname, Email etc.).

But what about the privileges highlighted in Cofense post that can be used to read emails, write files etc.? It’s so easy? The answer is: it depends.

To test Cofense scenario, configuration must be adjusted; set

Using a corporate account I cannot access Graph API also if the user authorized the app; an AuthenticationError comes back, good 😀

If the target is a personal account (e.g. @outlook.it) the Graph Mail API works like a charm, giving access to the user emails 🙂

Playing with Graph explorer gives the opportunity to find interesting API to play with (I added SCOPE and URL in the configuration files to query SharedWithMe endpoint too).

At the end the cloud opens new challenges for defense teams; there a lot of new possibilities for the attackers to compromise organizations and individuals and understanding how things work is a must for anyone in the blue side.

Finally. How can we detect such behavior? I use Microsoft CASB to detect new OAuth app with high privileges; then – after a review – I take the needed actions (revoke or grant access).

To be honest I think that organizations need to deny by default OAuth apps and authorize it only when needed. Too much danger around 😉

Have fun with the cloud!

One thought on “Phishing Microsoft cloud users using malicious apps and OAuth2

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.