Deep Diver – Azure AD Identity Protection (IPC) Alerts

This blog is all about Azure AD Identity Protection alerts (referred to provider name ”IPC” later on the blog post) in the Microsoft cloud ecosystem.

If you want to read how IPC works I encourage you to read my blog post or navigate straight to docs.microsoft.com.

IPC is an Azure AD P2 feature that has been in general availability mode for approx. three years. Earlier this year Microsoft did ”refresh” for IPC and added new detection capabilities and enhanced UI. Azure AD P2 feature means that it’s available in most expensive license packages.

You will get a taste of its features even with a free Azure AD license but all the cool features are included in AAD P2. Practically, IPC calculates user risk (online/offline) based on Machine Learning & AI and makes decisions based on policies, is user login approved, is MFA or password change needed or is user sign-in blocked.

Gimme The Alerts – Where are Those?

Azure AD Identity Protection (IPC) is a provider for multiple security solutions which means that alerts triggered in IPC can be found from multiple places (list below). Let’s have a closer look.

  • Azure AD Identity Protection blade
  • Intelligent Security Graph (ISG)
  • Azure Sentinel
  • Cloud App Security (MCAS)
  • Azure Security Center
  • Microsoft Threat Protection suite (MTP)
  • PowerShell for management

Azure AD Identity Protection Blade

Identity Protection UI resides in Azure AD where investigation and mitigations can be done. Btw, check my demo environment Identity Secure Score, 237 out of 265. Quite impressive even I say it myself 🙂

If a risk is detected admins (or dedicated email distribution list) will receive an alert as seen below. When a link is clicked, the person who received the email will land to the Identity Protection portal in Azure AD.

Intelligent Security Graph (ISG) aka Microsoft Security Graph

Identity Provider is one of the ISG providers, the full list of available providers at the time of writing can be found here. In pictures below are ISG high-level architecture and export from ISG alert from my tenant. In the latter one, you can see the provider highlighted with yellow color.

Microsoft Intelligent Security Graph (ISG)

Azure Sentinel

If you are using Azure Sentinel (a cloud-native SIEM which is a hot topic right now) and you have configured data connectors, and activated rule properly you will get IPC alerts to Azure Sentinel as incidents.

Azure Sentinel Identity Protection template rule basically raises an incident if an alert is generated in IPC.

Microsoft Cloud App Security

The IPC alert is also found from MCAS. As you can see from the picture below MCAS adds information to the alert and makes it more useful for the investigation. Btw, MCAS is the best solution in the Microsoft ecosystem to investigate internal user suspicious activity and behavior. If you are using it, I highly encourage you to get familiar with it and especially to UEBA capabilities.

I’m sold to it, totally:) But, I’m looking it only from the technical perspective, not from a financial perspective.

Azure Security Center (ASC)

IPC alerts are also found from Azure Security Center. ASC also provides a geolocation map which can be very useful to get a bigger picture of the attack.

Microsoft Threat Protection Suite (MTP)

MTP was just launched to public preview. Unfortunately, I don’t have such an environment available where it’s enabled. In a nutshell, it is a pre -and post-breach enterprise defense suite that natively integrates across endpoints, protecting:

  • Endpoints with Microsoft Defender ATP
  • Email and collaboration with Office 365 ATP
  • Identities with Azure ATP and Azure AD Identity Protection
  • Applications with Microsoft Cloud App security

More information from here and here.

PowerShell

This has slipped out of my radar totally. Microsoft released the PowerShell module for Microsoft Security Graph in April 2019. You can read more about it from here. In a nutshell, you need to do the following:

  • PowerShell v5 or above
  • Register App to Azure AD
  • User this URI: urn:ietf:wg:oauth:2.0:oob, it’s needed for desktop app redirect to work
  • Configure permissions to the App ( SecurityEvents.ReadWrite.All )
  • Grant Admin consent to the App
  • Install the module
  • Run and enjoy 🙂

In the Technet blog, there are multiple questions about App registration guidance. My 2cents:

  • When registering the App, grant API permissions for delegated mode (interactive login is used)
  • Grant SecurityEvents.Read.All & SecurityEvents.ReadWrite.All permissions to the App
  • Grant admin consent
  • When running the PowerShell – use you userprincipalName as username and AppID as password.

Using the module

Login with userPrincipalName + App ID. After the initial login, the modern authentication prompt appears (it’s encrypted by Finnish) and interactive login is processed together with Conditional Access policies.

Managing the Alerts

MicrosoftGraphSecurity module has following available commands

With Get-GraphSecurityAlert you can get all alerts from the ISG with Identity Protection alerts included.

Secure Score information is also available with Get-GraphSecuritySecureScore command.

Set ISG Alert

ISG alerts can be managed via PowerShell with Set-MicrosoftGraphSecurity cmdlet. Extremely useful if the alerts are sent to SIEM. The downside is that there isn’t any integration to the backends (providers).

Note: This means that even you update the alert status in ISG it will remain open in the ISG providers (Identity Protection, Cloud App Security etc.)

Summary

The Microsoft cloud ecosystem is huge and organizations have multiple security solutions available by default. Keep in mind that most of the advanced tools (including IPC, MCAS) require E5/A5, G5, P2 license. As seen above, synergy advantages are obvious.

What solutions to use is more a matter of cloud logging and monitoring strategy. From which sources you want to have an audit trail & events sent and to where? Where are you processing all the alerts, in SIEM or directly in Security solutions? And lastly, how operations are built around the solutions.

Until next time!

Azure API Management – JWT validation for multiple Azure AD partner registrations

If there is one popular theme regarding API management, it’s gotta be the subject of facilitating technically partner access via Azure AD. This can be as simple as creating a single app registration to designing full governance model with scopes & roles, and chained App Registrations.

As always, if you want to generally know what is Azure API management, or what is Azure Active Directory, then check the following articles:

Architecture

  • The architecture with new app endpoints enables same app registration to provide native and confidential client access with different settings
  • Scopes are defined per each partner registration

Create Centralized API Management Proxy SPN

  • Here we want to create single-tenant app in order to be able to control roles and authorizations within our tenant for B2B accounts. In multi-tenant scenario this is possible too, but requires that you control most of the authorizations in another management plane, or in scopes only
  • For redirect URI we input nothing, because this application will only act as Service Principal ”Front” for the actual app registrations consuming API management
  • We will use the FQDN of API management as displayName and AppID URL, in later calls we will use this as ’Audience /Resource for the partner clients”

Create Scopes and machine account role for the SPN

There is GUI to define scopes, but I have mine ready on template, so I am gonna use the ”old” experience updating via the manifest to create the scopes
oAuth2Permissions updated via the ”old experience” – Update ”Oauth2Permissions and AppRoles blocks”
 $apps = '{
  "oauth2Permissions": [
     {
         "adminConsentDescription":  "Allow the application to access PartnerAPI-Colda on behalf of the signed-in user.",
         "adminConsentDisplayName":  "Access PartnerAPI-Colda",
         "id":  "bc388acd-37a3-4358-8839-c84b4add4b23",
         "isEnabled":  true,
         "type":  "User",
         "userConsentDescription":  "Allow the application to access PartnerAPI-Colda on your behalf.",
         "userConsentDisplayName":  "Access PartnerAPI-Colda",
         "value":  "user_impersonation"
     },
     {
         "adminConsentDescription":  "Allow the application to access PartnerAPI-Colda on behalf of the signed-in user.",
         "adminConsentDisplayName":  "Access PartnerAPI-Colda",
         "id":  "40cfe68d-bef2-4a4b-8944-fc9766ba17d8",
         "isEnabled":  true,
         "type":  "User",
         "userConsentDescription":  "Allow the application to access PartnerAPI-Colda on your behalf.",
         "userConsentDisplayName":  "Access PartnerAPI-Colda",
         "value":  "user_impersonation2"
     },
     {
         "adminConsentDescription":  "Allow the application to access PartnerAPI-Colda on behalf of the signed-in user as reader.",
         "adminConsentDisplayName":  "Access PartnerAPI-Colda -reader",
         "id":  "a1203e4c-54bf-4e92-bb61-73e1e347cf9d",
         "isEnabled":  true,
         "type":  "User",
         "userConsentDescription":  "Allow the application to access PartnerAPI-Colda on your behalf as writer to the API.",
         "userConsentDisplayName":  "Access PartnerAPI-Colda -reader",
         "value":  "Reader"
     },
     {
         "adminConsentDescription":  "Allow the application to access PartnerAPI-Colda on behalf of the signed-in user as Writer.",
         "adminConsentDisplayName":  "Access PartnerAPI-Colda -writer",
         "id":  "b68e3e4f-7f45-497f-a815-6dc6ef46bbc8",
         "isEnabled":  true,
         "type":  "User",
         "userConsentDescription":  "Allow the application to access PartnerAPI-Colda on your behalf as Writer.",
         "userConsentDisplayName":  "Access PartnerAPI-Colda",
         "value":  "Writer"
     },
     {
         "adminConsentDescription":  "Service Account user Scope",
         "adminConsentDisplayName":  "Service Account App permissions",
         "id":  "508b851a-7393-478a-89b0-f83d1dcc825b",
         "isEnabled":  true,
         "type":  "Admin",
         "userConsentDescription":  "Allow the application to access PartnerAPI-Colda on your behalf as Writer.",
         "userConsentDisplayName":  "Access PartnerAPI-Colda",
         "value":  "Service_Accounts"
     }
                          ]
  }' | ConvertFrom-Json

  foreach ( $app in ($apps.oauth2Permissions)) {
  $app.id =([system.guid]::NewGuid()).guid
  }

  $apps |ConvertTo-Json -Depth 3

#Roles


$apps ='{
"appRoles":  [
      {
          "allowedMemberTypes":  [
                                     "application"
                                 ],
          "displayName":  "ReadOnly-ServiceAccount",
          "id":  "03b62322-1913-4c61-86a7-c325ebd5925f",
          "isEnabled":  true,
          "description":  "ReadOnly-ServiceAccount only to read data.",
          "value":  "ReadOnly-ServiceAccount"
      },
      {
          "allowedMemberTypes":  [
                                     "application"
                                 ],
          "displayName":  "ServiceAccounts",
          "id":  "a59b7301-039e-4782-ad1f-fc443cce1786",
          "isEnabled":  true,
          "description":  "Machine 2 Machine -type integrations.",
          "value":  "ServiceAccount"
      }
  ]
    
}' | ConvertFrom-Json 

  foreach ( $app in ($apps.appRoles)) {
  $app.id =([system.guid]::NewGuid()).guid
  }

  $apps |ConvertTo-Json -Depth 3

Create first partner application

  1. The first partner application is for typical use scenario of Client-Credentials Flow and Authorization Code Flow
From API permissions which we just defined on the manifest we can choose, what kind of user permissions (delegated) and application permissions we can set
In this case the partner needs full serviceAccount permissions, which I’ve set require admin consent
Since there is admin permission required I grant the directory access for the App. In the demo this will grant full scopes for the user. Later we can adjust the behavior of consents and pre-consents quite a lot if needed
Optionally, instead of ”allPrincipals consent” you have the option to leave the consent to the end user, if you’ve decided to make some of the scope end user consent -able (Example is the second partner API2)

Configure JWT validation for Partner Application

Each Partner is identified with their unique App ID claim. The control Any is used, to enable proxying of multiple Partner Clients. 401 is thrown if valid and signature checked AppID isnt found in the incoming Bearer Token
  • Note how we don’t need to reference the Proxy SPN at all? That’s because in previous step we’ve configured permissions for the Partner API to consume the Proxy SPN of API Management, and we need to reference the correct audience only

Test the API as a Partner using Code Authorization Flow

  • Response is what you would expect for Echo API’s create with POST-Verb
We are using ADAL binaries to invoke the code authorization flow
  • For PartnerClientID, we use the AppID we created in the previous step
  • We are using the public client redirect-uri to return the token for the user
This is how the Token Maps to JWT validation in Echo API

Test the API as a Partner using Client Credentials Flow

btw… don’t boughter with the client secret 🙂
  • Response is what you would expect for Echo API’s create with POST-Verb

Thats it for now! I will provide more insights, how to use multi-tenant apps, and how transform scopes into correlating back-end calls in the next blog

Deep Diver: Azure AD B2B

In this blog I focus on enterprise collaboration access scenarios, and demonstrate some peculiar insights Azure AD and AAD B2B hold in terms of issuing tokens

The Big Picture (click picture for larger version)

Click the picture for larger image

Disclaimer: The article focuses on delegated app permissions, thus you won’t see examples of App Identity & Client Credentials Flow. Neither should you assume that direct app permissions behave the same way as the scenarios demonstrated in the below paragraphs. The blog assumes previous understanding of Azure AD B2B (if you feel, that more is needed, read this excellent article describing the basics)

Tokens & Claims and resource access

Three stages of access?

Before we delve into B2B access, it’s important to understand how the resource access generally works in v1.0 endpoints with user identity driven scenarios

Click the picture for larger image

UserTypes?

UserType and Source have ”loose” correlation. Meaning you can always change the UserType. Source cannot be changed for existing account (not including the local federation), unless you delete the account, and do a specific invitation and on-boarding flow. By loose I mean, that invited user always correlates to non-native directory (but can be changed)

There is also userType OTP, which isn’t yet in the official documentation

B2B Specific general, tokens & claims articles:

MS provides only two very specific articles on these concepts when related to claims and tokens. I believe the reason is the generally excellent documentation for Azure AD Authz/authn scenarios, which is highly applicable

Under the hood?

Did you know, that you can make basically any global Azure AD tenant issue a Access Token for you’re account?… The token is worthless… Unless it held proper permissions scopes on the Client, and app specific ”acl-like” permissions for the requested application, like we demonstrated in the access stages part

What separates AAD B2B token from AAD token is that seperate IDP value is written to the B2B Token, since the final issuer is the destination directory, where the B2B user is collaborating, and not the users home directory

Lets take example from Teams

  • Flow differences
    • The local directory token is fetched against multi-tenant endpoint [common]
    • Guest token is fetched against the guest directory (issuer in our examples)

Guest token

{
    "aud": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    "iss": "https://sts.windows.net/46d2c4e6-a732-4fb4-b9f8-374af03f3f58/",
    "iat": 1557116305,
    "nbf": 1557116305,
    "exp": 1557120205,
    "acct": 1,
    "acr": "1",
    "aio": "AUQAu/8LAAAACFsh87mJ7wZTvQe5/qq5SnS+9srK3tO5sbt5leCu0sm5Rjfx41a9x7OQpn7HbrglieuT+bsL7db6hAEIvRb/XA==",
    "altsecid": "5::100320003B090681",
    "amr": [
      "pwd"
    ],
    "appid": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    "appidacr": "0",
    "email": "jose@ff00.info",
        "idp": "https://sts.windows.net/7fde6064-17a0-4c0e-87d6-19883001bd0d/",
    "ipaddr": "81.175.217.33",
    "name": "jose santa",
    "oid": "e648ddcb-2ea0-4a02-9a2e-ff491494846c",
    "puid": "1003200046F07AB7",
    "scp": "Contacts.ReadWrite.Shared Files.ReadWrite.All Notes.ReadWrite.All Sites.ReadWrite.All",
    "sub": "DYE_FXLPCgd1yPksPNFXov8b41jgZsedrc_W8t4bcMA",
    "tid": "46d2c4e6-a732-4fb4-b9f8-374af03f3f58",
    "unique_name": "jose@ff00.info",
    "uti": "rdex3UDBd0yBZXI5XfVEAA",
    "ver": "1.0"
  }

Local token

{
    "aud": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    "iss": "https://sts.windows.net/7fde6064-17a0-4c0e-87d6-19883001bd0d/",
    "iat": 1557116092,
    "nbf": 1557116092,
    "exp": 1557119992,
    "acct": 0,
    "acr": "1",
    "aio": "AVQAq/8LAAAAjpEyijYGeGtKYtJ2cqQfvZpmcc5AtM+g/REMI506RwnVm6GanjOcuGmG2iv97UygfkTI/ngALSC5pEZBlmJg1xpCgujBXkajEbKmYpjlWZA=",
    "amr": [
      "pwd",
      "mfa"
    ],
    "appid": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    "appidacr": "0",
    "family_name": "santa",
    "given_name": "jose",
    "ipaddr": "81.175.217.33",
    "name": "jose santa",
    "oid": "2995f48b-8c49-4cf9-aabc-21aef5661e15",
    "onprem_sid": "S-1-5-21-621967805-835074083-177589206-1153",
    "puid": "100320003B090681",
    "scp": "Contacts.ReadWrite.Shared Files.ReadWrite.All Notes.ReadWrite.All Sites.ReadWrite.All",
    "sub": "27ASPxuGbUTB8DlgO_kAFeoZkAUbZBkah5JhvFN5nDc",
    "tid": "7fde6064-17a0-4c0e-87d6-19883001bd0d",
    "unique_name": "jose@ff00.info",
    "upn": "jose@ff00.info",
    "uti": "AEIFj-kI00-InbpiPzlBAA",
    "ver": "1.0"
  }


Stranger questions in a Strange Land:

  • What happens when B2B users UPN changes?
    • Answer:According to my testing, the user mapping isn’t done based on UPN, but other immutable-like ID (There are bunch of those in the token claims) – Thus If the UPN changes, the mapping will still work, but it will reflect the initial UPN of the user
  • Can I do access control on group based role claims emitted from guest tenant?
    • answer: I haven’t tested this yet, but I would speculate that only the roles of the destination tenant are available, not roles from the IDP tenant (for the guest user) – Maybe there is ”exposed groups/roles” -like feature in the roadmap, that allows a scoping a select set of group-values to flow cross-tenant?
  • Which claims I shouldnt excpect to flow cross tenant in SAML federations
  • How Google and OTP authenticated users differ from other B2B sources?
    • Answer: Both require tenant context for sign-in requests, meaning common endpoints, such portal.azure.com myapps.microsoft.com eg. wont work without the destination tenant appended in the URL
https://docs.microsoft.com/en-us/azure/active-directory/b2b/one-time-passcode
https://docs.microsoft.com/en-us/azure/active-directory/b2b/google-federation
  • Can I prevent my users collaborating on another tenants?
    • Answer: You can deny external guest accounts collaborating on you’re directory, but you can’t deny you’re users collaborating on other tenants (Unless you allow very few apps, that don’t support B2B collaboration on conditional access for the users, and deny all other apps)
    • Short answer: Inbound collaboration is controllable, outbound isn’t
  • Does the MFA performed in guest tenant satisfy the MFA requirement on the guest tenant
    • Answer: No, and for good reasons. You cant simply know, in what way the guest tenant user has satisfied the MFA requirement
  • I am developing Azure AD App where I need to control groups and App Roles in Azure AD related authorizations. Which App type should I use: single, or multi-tenant?
    • Answer: With single tenant app using guest users all app roles and group permissions are on you’re control. You can also apply local conditional access policy to the application and its b2b users, which you can’t do in multi-tenant apps for B2B users
    • More: With multi-tenant app you don’t need to worry about having B2B collaboration accounts on you’re directory, but you need to build the authorization and roles directly in the app, if want retain similar control.
    • Even more: There are many scenarios where it makes sense to develop app using both app types. The multi-tenant app can act as initial staging proxy, to invoke the invitation API, enabling complete self-service on-boarding for example select group of users
  • Can we use B2B users in Azure AD Domain Services
    • Answer:
https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-faqs#can-guest-users-invited-to-my-directory-use-azure-ad-domain-services

Ending words

If you felt that my article didn’t cover enough, or felt vague in some areas check these excellent articles from MS.

I plan also to update the article on continuous basis, so I will try to post any updates on the article on future as well.

B2B User Types – Properties of an Azure Active Directory B2B collaboration user

B2B High Level – What is guest user access in Azure Active Directory B2B?

UserType OTP – Email one-time passcode authentication (preview)

Br, Joosua

Enable Microsoft Security Graph Alerts in Log Analytics

While its not yet configurable in GUI, you can already today configure (with proper prerequisites) the preview for Security Graph API and Log Analytics Integration.

Side note: Similar Integration can be done for Event Hub, and its recommended while enabling LA integration to enable EH integration (if EH exists in tenant)
https://docs.microsoft.com/en-us/graph/security-qradar-siemintegration

While its not yet configurable in GUI, you can already today configure (with proper prerequisites) the preview for Security Graph API and Log Analytics Integration.

Alerts today include  (Check link for original reference)

https://docs.microsoft.com/en-us/graph/api/resources/security-api-overview?view=graph-rest-1.0

I will avoid using the word Azure Monitor here, as this integration is not really in my opinion tapping into any resource API in the Azure Monitor (unless you consider Azure Monitor as umbrella term

AAD Identity Protection Alert
Same alert in Log Analytics
The target in event hub ( I will do separate blog about ELK integration)

integration includes also Event Hub – which has been available for a good while already via similar method

Before proceeding: As always – read the disclaimer in the bottom of the page) I put the disclaimer up also here to emphasize its matter, not just as an escape clause

How To: integration with Log Analytics

  • Disclaimer: The behavior may vary per tenant
  • It took few hours till events started popping up in Log Analytics and event hub
  • To enable the integration You can use existing AAD tokens in cache to achieve this
    • I’ve usually preffered the ADAL libraries to do this bit more simply, but in this case we want to use the AzureRM module to populate parts of the script simply (/done similarly here)


Login-AzureRmAccount
$ctx =Get-AzureRmContext
Get-AzureRmSubscription | Out-GridView -PassThru |Set-AzureRmContext

$rmEndpoint ="https://management.azure.com/providers/Microsoft.SecurityGraph/diagnosticSettings/securityApiAlerts?api-version=2017-04-01-preview"

#Details from IAM API
$Token = Invoke-RestMethod "https://login.windows.net/common/oauth2/token" -Method POST -Body @{
   grant_type="refresh_token"
   refresh_token = ($ctx.TokenCache.ReadItems() | Out-GridView -PassThru ).refreshToken
   content_type = "application/json"
   resource="https://management.core.windows.net/"
}

$json = '
{
  "location": "",
  "properties": {
      "workspaceId": null,
    "name": "securityApiAlerts",
    "serviceBusRuleId": "/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP/providers/Microsoft.EventHub/namespaces/EVENT_HUB_NAMESPACE/authorizationrules/RootManageSharedAccessKey",
    "logs": [
      {
        "category": "Alert",
        "enabled": true,
        "retentionPolicy": {
          "enabled": true,
          "days": 7
        }
      }
    ]
  }
}
' | ConvertFrom-Json 

$json.properties.serviceBusRuleId = ( (Get-AzureRmResource | where {$_.ResourceType -match "Eventhub"}| Out-GridView -PassThru).resourceID  + "/authorizationrules/RootManageSharedAccessKey")

$json.properties.workspaceId = ((Get-AzureRmOperationalInsightsWorkspace | Out-GridView -PassThru ).resourceID)

$data = Invoke-RestMethod -Uri $rmEndpoint -Method Put -Body ($json | ConvertTo-Json -Depth 4) -UseBasicParsing -Headers @{
Authorization = ($Token.token_type) +" "+ ($Token.access_token)
} -ContentType "application/json; charset=utf-8"



Comment from @samilamppu

its quite likely, that you need to have Security Center Standard with proper event coverage enabled, to get the Node for security alerts under LA

Br,

Joosua

You can’t hide things in AAD

If there is single sentence I would emphasize regarding all things AAD, then it would be ”You can’t hide things in AAD” – This sentence is based on the premise, that in order for services and users to work and interact there has to be visibility between objects in the directory.

One of the most common examples is, that sometimes a new hire in a large company directory has been ”hidden” by setting ”msExchHideFromAddressLists” value to true. This done in order to hide the user from Exchange Address List, but that really is the only thing that setting does. It does absolutely nothing to hide the user from AAD, (or from on-prem AD),

Azure AD Directory Members can enumerate lot of stuff in AAD

  1. All other users in the tenant, including guest users
    1. In a large tenant, there might be external users in as non-guest members, they can query all the same stuff than the internal users. For example, they can see who of their competitors are also working at the same customer, and if the customer is using Office 365 Groups, then they might by just looking at group names, descriptions and members deduct some insights of the project
  2. Groups, and group members
  3. Applications and permissions registered for the tenant
  4. All visible user attributes (this mainly only excludes the Authentication attributes)
    1. Mobile Phones
    2. Alternative Email Address
    3. Alias Addresses
  5. And also see occurred changes in all above objects by using the delta listeners in Graph API

Graph API

GraphAPI

Lets start by saying that Graph API is The most powerful invention Microsoft has devised in the last few years. You can already see that many of the Microsoft Client Apps fetch their oAuth2 Access Tokens with resource graph defined as their preferred resource. If you’re AAD or Office Dev this thing is going make you’re life so much easier! For me it represents the greatest thing since sliced bread :)…

But…

This means, that when such powerful API exists, there must be also in-depth understanding of it’s capabilities:

Below is demo of a standard user creating a ”listener for all delta changes” in the directory.

One can only imagine, how much an persistent presence having attacker would benefit on this kind of listener, that lists delta changes such as addition of new groups, phone numbers, title changes, changes in group memberships etc.

 

What standard non-privileged user can get by using delta subscription?

  • All new attribute changes

Attrs.PNG

  • All new groups, and their members, or just group member changes

DeltaObjects3

  • New invited users from other tenants

External.PNG


PoC Script

Description

Query Any Delta Change for directory objects, after you’ve defined what to listen first in the initial request

#User permissions
Any internal user will work, regardless Directory Role = Normal, even unlicensed user account can do this

#ADAL
you must have ADAL library files to run this example

#App parameters and endpoints
Global Powershell Multitenant = ClientID 1b730954-1685-4b74-9bfd-dac224a7b894
redirect URI’s = ”urn:ietf:wg:oauth:2.0:oob”
Endpoint = https://login.windows.net/common/

#Scopes
”scp”: ”AuditLog.Read.All Directory.AccessAsUser.All Directory.ReadWrite.All Group.ReadWrite.All
-When you use Powershell as ClientID you will get these static scopes for all users, regardless of the user permissions (User Permissions are separately then checked against AAD)

#Microsoft documentation
https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_delta
https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/group_delta

  • Read MS docs to understand how delta subscriptions work.
  • You can’t just run the script with F5 in ISE, or execute directly. The script is meant to be used so that you create subscription, and then run another row on the script to get deltas added into existing array
  • I could finesse the script bit more, but for now its used to demonstrate how non-privileged user can use it just like an admin user

 


Add-Type -Path $env:USERPROFILE\AzureADToken\Microsoft.IdentityModel.Clients.ActiveDirectory.2.12.111071459\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll 

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.windows.net/common/"
$ThisPromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always
$authResult = $AuthContext.AcquireToken("https://graph.microsoft.com","1b730954-1685-4b74-9bfd-dac224a7b894", "urn:ietf:wg:oauth:2.0:oob", $ThisPromptBehavior)

#URI to use the access token
$InitialURI = "https://graph.microsoft.com/v1.0/users/delta"
$GroupsURI = "https://graph.microsoft.com/beta/groups/delta"

 $Headers = @{
 "Authorization" = ($authResult.AccessTokenType) +" "+ ($authResult.AccessToken)
 }

#Subscribe for changes you're interested user objects
$req = Invoke-RestMethod -Method Get -Uri $uri -UseBasicParsing -Headers $headers -Verbose
#Subscribe for changes you're interested in groups
$greq = Invoke-RestMethod -Method Get -Uri $GroupsURI -UseBasicParsing -Headers $headers -Verbose
#query for all changes, and populate results to array
$ChangeArray = @();$count=$null

do {
$count = $count + $req.value.Count;$count
 $req = Invoke-RestMethod -Method Get -Uri $req.'@odata.nextLink' -UseBasicParsing -Headers $headers -Verbose

 $ChangeArray += $req.value
 $req.value

 Write-Host "Next Link"

    } while ($req.'@odata.nextLink' -ne $null)

do {
$count = $count + $greq.value.Count;$count
 $greq = Invoke-RestMethod -Method Get -Uri $greq.'@odata.nextLink' -UseBasicParsing -Headers $headers -Verbose

 $ChangeArray += $greq.value
 $greq.value

 Write-Host "Next Link"

    } while ($greq.'@odata.nextLink' -ne $null)

Br Joosua!