In our present threat landscape, attackers are constantly trying to compromise organizations, each with their own set of motives. They may want to compromise accounts credentials to later sell, utilize compromised users to spread phishing campaigns, or even utilize the computing resources of the organization to deploy and run their own tooling. In this blog, we will describe how attackers can compromise Azure subscriptions and use them for malicious activities. In addition, we will share how Microsoft Defender for Cloud Apps data can help hunt for these activities and how to mitigate the risk of compromised subscriptions.
We will focus on 3 main scenarios of how a subscription can be compromised and/or hijacked. All the scenarios have shared similarities but also a few differences.
In this scenario, an attacker has compromised a user with sufficient permissions to create a new subscription and/or manage an existing one (owner/contributor over a subscription). The admin can be compromised through leaked credentials, guessing credentials, stolen tokens, etc. We observed in most cases, the attackers are choosing to create a new subscription instead of using an existing one (we assume they are doing so in order to reduce the chances of being notices). By using the compromised user, the attacker can access the subscription through the portal and/or Azure Management API and deploy their resources; for example, deploying VM’s for crypto-mining activities.
The first stage of this scenario is the same as the previous one. An attacker has compromised an admin user and used it to create and/or takeover an existing subscription. Unlike the first scenario, this time the attacker tries to be stealthier and create a persistence mechanism by inviting an external user to the tenant. Then, the attacker promotes the external user to privileged roles. At this point, the attacker is using the external user to perform actions in Azure. In some cases, instead of inviting a guest account, the attackers are creating a new account in the tenant.
Let’s see the attack flow in a graph:
For this scenario, we first need to understand some basic concepts.
What is subscription transferring?
Organizations might have several Azure subscriptions. Each subscription is associated with a particular Azure Active Directory (Azure AD) directory (tenant). To make management easier, many of our current customers need to be able to transfer a subscription to a different Azure AD tenant. The subscription transformation can be completed through the azure portal:
Figure 1 - Changing a directory of Azure subscription through the portal
Several Azure resources have a dependency on a subscription or a tenant. When transferring subscriptions, some of the resources and RBAC assignments are affected. For example, when transferring the subscription, all the role assignments of the subscription are deleted as documented here.
Another important nuance is that while the subscription is being transferred to another tenant, the billing account will remain the same unless it is manually modified, meaning that while the subscription is managed by different, the original tenants’ billing account is charged:
Figure 2 - warning message as seen on Azure portal
Attackers have been observed utilizing the subscription transferring feature to hijack subscriptions from compromised tenants by transferring these subscriptions to attacker-controlled tenants. After the attackers own the hijacked subscriptions, attackers can utilize the subscription to deploy new resources for malicious activities.
Attack flow in graphs:
What makes this technique relatively stealthy is the fact that after the subscriptions move to a different tenant, all the activity logs of the hijacked subscriptions will be routed to the attacker tenant.
After learning about the attack surface, let’s see how we can proactively hunt and detect potential subscription compromise attempts and their related events.
2 of the 3 mentioned scenarios include inviting malicious external accounts.
First, Defender for Cloud Apps customers can monitor all external accounts using the Defender for Cloud Apps portal under “Investigate” -> “Users and accounts” and filter for “external users” and “show admins only”:
In addition, using ‘Advanced Hunting’ in M365D portal, we can hunt for users who added guest accounts to the tenants:
CloudAppEvents | where Timestamp > ago(7d) | where ActionType == "Add user." | where RawEventData.ResultStatus == "Success" | where RawEventData has "guest" and RawEventData.ObjectId has "#EXT#" | mv-expand Property = RawEventData.ModifiedProperties | where Property.Name == "AccountEnabled" and Property.NewValue has "true" | project Timestamp, AccountObjectId, AccountDisplayName, newGuestAccount = RawEventData.ObjectId, UserAgent
If we want to filter further, we can search for new guest accounts who have been promoted to certain AAD roles:
let Roles = pack_array("Company Administrator"); let newGuestAccounts = ( CloudAppEvents | where Timestamp > ago(7d) | where ActionType == "Add user." | where RawEventData.ResultStatus == "Success" | where RawEventData has "guest" and RawEventData.ObjectId has "#EXT#" | mv-expand Property = RawEventData.ModifiedProperties | where Property.Name == "AccountEnabled" and Property.NewValue has "true" | project CreationTimestamp = Timestamp, AccountObjectId, AccountDisplayName, newGuestAccount = RawEventData.ObjectId,newGuestAccountObjectId = tostring(RawEventData.Target.ID), UserAgent); let promotedAccounts = ( CloudAppEvents | where Timestamp > ago(7d) | where isnotempty(AccountObjectId) | where ActionType == "Add member to role." | where RawEventData.ResultStatus == "Success" | where RawEventData has_any(Roles) | where RawEventData.Actor has "User" | project PromoteTimestamp = Timestamp, PromotedUserAccountObjectId = tostring(RawEventData.Target.ID)); //join the two tables newGuestAccounts | join promotedAccounts on $left.newGuestAccountObjectId == $right.PromotedUserAccountObjectId | where PromoteTimestamp > CreationTimestamp | project CreationTimestamp, PromoteTimestamp, PromotedUserAccountObjectId, newGuestAccount, newGuestAccountObjectId
One of the most common resources deployed by attackers in compromised subscriptions are virtual machines. Let’s hunt for a new external user who has deployed at least X virtual machines:
let threshold = 3; let newGuestAccounts = ( CloudAppEvents | where Timestamp > ago(7d) | where ActionType == "Add user." | where RawEventData.ResultStatus == "Success" | where RawEventData has "guest" and RawEventData.ObjectId has "#EXT#" | mv-expand Property = RawEventData.ModifiedProperties | where Property.Name == "AccountEnabled" and Property.NewValue has "true" | project newGuestAccountObjectId = tostring(RawEventData.Target.ID) | distinct newGuestAccountObjectId); CloudAppEvents | where Timestamp > ago(7d) | where isnotempty(toscalar(newGuestAccounts)) | where Application == "Microsoft Azure" | where ActionType == "Validate Deployments" | where RawEventData contains "createVm" | where AccountObjectId in (newGuestAccounts) | summarize VMCreationCount = count() by AccountObjectId | where VMCreationCount > threshold
Next, we can search users who have high risk sign-in activity, according to AAD Identity Protection, and have created a resource group (which is indicator of subscription creation):
let riskyAzureSignIns = ( AADSignInEventsBeta | where Timestamp > ago(7d) | where ErrorCode == 0 | where Application == "Azure Portal" | where RiskLevelAggregated == 100 or RiskLevelDuringSignIn == 100 | project AccountObjectId, RiskySignInTimestamp = Timestamp); let resourceGroupCreation = ( CloudAppEvents | where Timestamp > ago(7d) | where Application == "Microsoft Azure" | where ActionType == "Write ResourceGroup" | project AccountObjectId, ResourceGroupCreation = Timestamp); //join the tables riskyAzureSignIns | join resourceGroupCreation on AccountObjectId | where ResourceGroupCreation between (RiskySignInTimestamp .. (RiskySignInTimestamp + 12h))
Defender for Cloud Apps enable you to create a custom activity policies and craft your own logic. With custom policies, we can build alerts and take automatic actions on those users. Let’s take a look at a few examples.
In this case, we want to create an alert and send email notification every time an external user has created a new virtual machine. We will define the severity as 'low' as we still want to track this activity while still being able to focus on higher severity alerts.
Defender for Cloud Apps allows admins to work with IP ranges and tag them accordingly. Some of the IP addresses will be tagged as ‘Risky’ by threat intelligence sources. In the following policy, we will alert users who created virtual machines from a risky IP address.
There following alerts could be related to the scenarios we described above, with the ones listed in bold being the most critical to monitor.
We observed that almost all of the compromised admins in subscription compromised attacks did not have MFA enabled, which would have helped combat the most common initial access vector of guessing a user’s credentials. The following recommendations help significantly reduce the risk of credential guessing attacks.
Conditional access policies are evaluated and enforced every time an attacker attempts to sign-in. Organizations can protect themselves from attacks that leverage stolen credentials by enabling policies such as compliant devices or trusted IP address requirements. In the above example, the attacker’s sign-ins were assessed as high risk. Conditional access can be used to block or require MFA for sign-ins that Azure AD Identity Protection detects are risky in real time.
Continuous Access evaluation (CAE) revokes access in real time when changes in user conditions trigger risks, such as when a user is terminated or moves to an untrusted location.
Let us know if you have any other hunting scenarios you'd be interested in seeing. We would love to hear your feedback!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.