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


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 :)…


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


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


  • New invited users from other tenants


PoC Script


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

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 =

”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

  • 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 ""
$ThisPromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always
$authResult = $AuthContext.AcquireToken("","1b730954-1685-4b74-9bfd-dac224a7b894", "urn:ietf:wg:oauth:2.0:oob", $ThisPromptBehavior)

#URI to use the access token
$InitialURI = ""
$GroupsURI = ""

 $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

 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

 Write-Host "Next Link"

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

Br Joosua!