Skip to main content
Git-Rotate: Leveraging GitHub Actions to Bypass Microsoft Entra Smart lockout

Git-Rotate: Leveraging GitHub Actions to Bypass Microsoft Entra Smart lockout

Daniel Underhay
Password Spraying IP Rotation
Before proceeding, it’s important to keep in mind that GitHub takes abuse and spam of Actions seriously, and they have a dedicated team to track “spammy users”. I am not liable for any account closures due to breaches of their terms and conditions.


Despite advancements in cybersecurity, password spraying attacks remain a prevalent and effective technique for attackers attempting to gain unauthorised access to cloud-based infrastructure and web applications by targeting their login portals. Password spraying involves attempting a small number of common passwords against a large number of usernames. This makes it difficult for security systems to detect and mitigate as they often avoid common protections such as account lockout policies by avoiding rapid or repeated login attempts for a single account. Attackers can easily obtain lists of commonly used passwords or use automated tools to generate potential passwords, increasing the likelihood of success.

I recently rewrote MSOLSpray (originally written in PowerShell) as a Python script for cross-platform use, which you can find here. One challenge in password spraying attacks is the need to rotate IP addresses to avoid detection by security mechanisms such as IP-based blocking. By rotating IP addresses for each request, attackers can distribute their login attempts across a range of unique addresses, making it harder for defenders to detect and block the attack. There are several existing tools to handle this such as fireprox, fireproxng and Burp suite’s IP rotate extension. However, this got me interested in the topic of IP rotation and I had a discussion with some colleagues, thinking of different ways to achieve a similar result. I set the following criteria:

  • Rotate IP address with each request (or a maximum number of requests);
  • Has an API (can be used programmatically);
  • Has to be cheap or, better, free; and
  • Has a large IP address space.

GitHub Actions Overview

GitHub Actions is an automation tool provided by GitHub that allows users to automate software workflows. Workflows are configurable automated processes that can run one or more jobs. These workflows are defined by a YAML file checked into your repository and can be triggered by various GitHub events, such as code pushes, issues being created, or even manually via a REST API. Workflows are defined in the .github/workflows directory in a repository, and a repository can have multiple workflows, each of which can perform a different set of tasks or actions. These actions can be configured to run in a Virtual Machine (VM) or a container, allowing you to run arbitrary code, such as Python scripts, which has been abused by people in many interesting ways (crypto-mining, hosting c2 infrastructure, etc). However, I have not yet seen anyone using GitHub Actions for distributed password spraying.

According to the documentation, GitHub Actions usage is free for both storage and execution time for public repositories, regardless of the user’s plan. This means that free GitHub users can use GitHub Actions on public repositories without any limits on execution time. Free GitHub users get 2,000 minutes of GitHub Actions execution time per month for private repositories (for jobs that run on Linux Operating System).

Note: GitHub rounds the minutes and partial minutes each job uses up to the nearest whole minute. So despite the workflow run taking a few seconds to execute, the billing time is rounded up to the nearest minute. Keep this in mind if you are going to perform large spraying attacks from a private GitHub repository. At the time of writing, the per-minute rate (USD) is $0.008 for a Linux OS with 2 vCPUs.

GitHub Actions execute workflow runs on virtual machines (VMs) hosted by GitHub, each of which is assigned a network interface and an IP address from GitHub’s address pool at random. This IP address is used for activities that require internet access during the workflow run. For the most current list of GitHub’s IP address ranges, you can refer to the GitHub Meta API.

There is a well-documented REST API which can be used to interact with GitHub Actions for an organisation or repository.

Looking back at the required criteria defined earlier, GitHub Actions seems to be a good candidate. It provides a cost-effective (free) and programmable way to rotate IP addresses for password spraying attacks.

Requirement GitHub Actions
Rotate IP address with each request
Has an API (can be used programmatically)
Has to be cheap or free
Has a large IP address space

Microsoft Login Portal

The Microsoft login portal is a prime target for attackers due to its widespread use and the valuable information it holds. Microsoft accounts are used for accessing a variety of services, including email, cloud storage, and productivity tools, making them a treasure trove of sensitive data. Additionally, Microsoft’s authentication mechanisms, while robust, are not immune to vulnerabilities, and attackers often exploit weaknesses such as weak passwords or lack of multifactor authentication to gain unauthorised access.

Microsoft Entra’s Smart Lockout feature is designed to combat password spraying attacks and according to the documentation does not solely rely on blocking IP addresses for prevention; instead, it employs a combination of factors:

Smart lockout helps lock out bad actors that try to guess your users’ passwords or use brute-force methods to get in. Smart lockout can recognize sign-ins that come from valid users and treat them differently than ones of attackers and other unknown sources.

Smart Lockout uses familiar location vs unfamiliar location to differentiate between a bad actor and the genuine user. Both unfamiliar and familiar locations have separate lockout counters.

There are some pretty vague terms such as “unknown sources” and “unfamiliar location;” these allude to IP addresses or at least Geo-location. Whilst the exact checks performed by smart lockout is not publicly known, at least one element of smart lockout’s differentiation between familiar and unfamiliar locations does appear to be based on the origin IP address. There may be additional factors such as the device used (perhaps based on the user-agent), the user’s behaviour patterns (such as what client-id / app is being accessed), and other contextual information. However, during testing I successfully bypassed smart lockout controls using GitHub Actions after the account was “locked,” which you can see in the demo below.

While I have used the Microsoft login endpoint as a target in the demo, this technique can be applied to any target where IP-based blocking needs to be bypassed during brute-forcing attacks.

Project Components

The project is divided into three components; the kicker, the sprayer, and the catcher.

  • Kicker: The kicker is a Python script responsible for creating and populating the workflow runs inside GitHub Actions. When generating the workflow run, it passes the relevant information, such as username, password, and the IP address of the catcher. We can use GitHub secrets to pass ‘sensitive’ information to the workflow without exposing it in logs. GitHub Actions allows multiple workflow runs within the same repository, which means we can use the API to generate a large number of workflow runs, each performing a login request from a different IP address.

  • Sprayer: The sprayer is a Python script executed by a GitHub Actions workflow run. It is responsible for sending a login request to the target endpoint (e.g., with a username and a password combination, retrieves the response from the login attempt, and forwards it to the catcher component. An IP address is randomly assigned to the VM executing the workflow run, ensuring that each request is sent with a unique IP address to evade detection.

  • Catcher: The catcher is a Python Flask web server that receives and processes login response data sent by the sprayer component. It determines the success or failure of the login attempts and logs the data appropriately. Note: Caddy is used as a reverse proxy in this setup to manage incoming requests and forward them to the Flask web server. It’s simple to set up and handles TLS certificates. This provides a convenient solution ensuring secure communication between the Sprayer and Catcher.

  • The Target below can be any login endpoint you are performing password spraying against or whenever you are brute-forcing something and wanting to avoid IP-based blocking.

%%{init: {'theme':'forest'}}%% sequenceDiagram autonumber Kicker->>Sprayer: Login details (username & password) Sprayer->>Target: Login Request Target-->>Sprayer: Login Response Sprayer->>Catcher: Login Response loop Response Processing Catcher->>Catcher: Process login response data and write logs end


A Microsoft tenant with the Entra ID P2 license was used for testing. Microsoft Entra smart lockout was configured with the default settings of 10 incorrect login attempts with a 60 minute lockout.

Entra smart lockout settings

Let’s have a look at it in action.

We can validate that login requests are being made via GitHub Actions IP addresses.

testing catcher

You can find all the project components in the following GitHub repository.




The information in this article is provided for research and educational purposes only. Aura Information Security does not accept any liability in any form for any direct or indirect damages resulting from the use of or reliance on the information contained in this article.