HelSec Azure AD write-up: Phishing on Steroids with Azure AD Consent Extractor

Here is short summary of the Presentation slides and demo of the Azure AD Consent Extractor @HelSec

  1. Attackers sends OWA upgrade link
  2. Victim clicks the link and accepts the upgrade
  3. Victim is redirected back to OWA when hitting the attacking server
  4. Victim receives ”OWA upgrade complete” message to email
  5. Meanwhile the attacking server is enjoying its new privileges on the user account

See the attack in action

If you want to know more about the consent attack, you can read my previous blog post from 2018

This version is the updated version to the one I did in 2018


Add sAMAccountName to Azure AD Access Token (JWT) with Claims Mapping Policy (and avoiding AADSTS50146)

With the possibilities available (and quite many of blogs) regarding the subject), I cant blame anyone for wondering whats the right way to do this. At least I can present one way that worked for me

Here are the total ways to do it (1. obviously not the JWT token)

  1. With SAML federations you have full claims selection in GUI
  2. Populate optional claims to the API in app registration manifest, given you’ve updated the schema for the particular app
  3. Create custom Claims Policy, to choose emitted claims (The option we’re exploring here)
  4. Query the directory extension claims from Microsoft Graph API appended in to the directory schema extension app* that Graph API can call

Please note, for sAMAccountName we’re not using the approach where we add directory extensions to Graph API queryable application = NO DIRECTORY EXTENSION SYNC IN AAD CONNECT NEEDED

Checklist for using Claims Mapping Policy

Pre: Have Client application, and web API ready before proceeding

#Example App to Add the Claims 
$Definition = [ordered]@{
    "ClaimsMappingPolicy" = [ordered]@{
        "Version" = 1
        "IncludeBasicClaimSet" = $true
        "ClaimsSchema" = @(
                "Source" = "user"
                "ID" = "onpremisessamaccountname"
                "JwtClaimType" = "onpremisessamaccountname"
$pol =  New-AzureADPolicy -Definition ($definition | ConvertTo-Json -Depth 3) -DisplayName ("Policy_" + ([System.Guid]::NewGuid().guid) + "_" + $template.Values.claimsschema.JwtClaimType) -Type "ClaimsMappingPolicy" 
$entApp =  New-AzureADApplication -DisplayName  ("DemoApp_" + $template.Values.claimsschema.JwtClaimType)
$spnob =  New-AzureADServicePrincipal -DisplayName $entApp.DisplayName -AppId $entApp.AppId 
Add-AzureADServicePrincipalPolicy -Id $spnob.ObjectId -RefObjectId $pol.Id 
#From the GUI change the Identifier and acceptMappedClaims value (From the legacy experience)

  • Generally: The app that will emit the claims is not the one you use as the clientID (Client subscribing to the Audience)
    • Essentially you should create un-trusted client with clientID, and then add under Api permissions the audience/resource you’re using
  • Ensure that SPN has IdentifierURI that matches registered custom domain in the tenant
    • The reasoning is vaguely explained here & here
      • Whatever research work the feedback senders did, it sure looked in depth 🙂
  • Update the app manifest to Accept Mapped Claims
    • Do this in the legacy experience, the new experience at least in my tenant didn’t support updating this particular value
”Insufficient privileges to complete the operation”

if mapped claims are not accepted in manifest, and pre-requisites are not satisfied you might get this error

”AADSTS50146: This application is required to be configured with an application-specific signing key. It is either not configured with one, or the key has expired or is not yet valid. Please contact the application’s administrator.”

  • Below is example for the Manifest changes (AcceptMappedClaims, and verified domain matching URI)
     "id": "901e4433-88a9-4f76-84ca-ddb4ceac8703",
    "acceptMappedClaims": true,
    "accessTokenAcceptedVersion": null,
    "addIns": [],
    "allowPublicClient": null,
    "appId": "9bcda514-7e6a-4702-9a0a-735dfdf248fd",
    "appRoles": [],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2019-06-05T17:37:58Z",
    "groupMembershipClaims": null,
    "identifierUris": [


If you’re planning to use non-verified domain based identifier

”AADSTS501461: AcceptMappedClaims is only supported for a token audience matching the application GUID or an audience within the tenant’s verified domains.







Decode JWT access and id tokens via PowerShell

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 = https://login.windows.net/common/

”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 "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

 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!

Tips&Tricks- Securing Activesync Access to Exchange Online with 365 MDM

I’ve always felt that Office 365 MDM is obsolete option between doing just Exchange ActiveSync Policies, and going the whole nine yards with true MDM capabilities (Intune)

Today I realized that you can do at least one thing with o365 MDM, separating it from plain Exchange mobile policy, and that is securing ActiveSync enrollment




Most (or all) mobile OS’s apart from native iOS mail-client are missing modern auth capable native mail client, thus securing ActiveSync enrollment with 2FA is sometimes seen as bit of an question.

It’s usually addressed with one, or combination of some of the following ways:

  1. By using Outlook mobile app
  2. By using app passwords ( I wouldn’t recommend app passwords even to my worst enemy)
  3. By Intune enrollment requirement
  4. By bypassing activesync for 2FA, and requiring admin release from quarantine
  5. Just bypassing activesync for 2FA

Now I am adding one more option to the list, (6) –  Office 365 MDM – Which is pretty much my top recommendation, if you need to use non iOS native mobile clients and/or don’t have Intune available

  • If you’re using Azure AD Premium P1, or 3rd party MFA with AD FS, and wan’t to offer strong enrollment before allowing ActiveSync access, but don’t have Intune, then I see this as pretty tempting way of achieving some additional security for ActiveSync:

Client experience

  • After adding the ActiveSync profile to the mobile phone user receives very similar mail to the classic quarantine message. This is only item in the mailbox. Thus no data is exposed to the user until further verification (2 FA requiring enrollment is done)


Service side settings

And in order for this setup to work

  • Block non-Office365 MDM supporting devices


  • Company Portal App and device join (Intune) has to require second factor, otherwise the protecting qualities of this setup are done
  • If you wan’t to allow non-ActiveSync clients in AD FS 3rd party create filtering rule, or configure CA to filter out ActiveSync in ’Client Apps’ settings


Thats it!

Br, Joosua