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 

AzureADPreview\Connect-AzureAD

$Definition = [ordered]@{
    "ClaimsMappingPolicy" = [ordered]@{
        "Version" = 1
        "IncludeBasicClaimSet" = $true
        "ClaimsSchema" = @(
            [ordered]@{
                "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": [
        "https://samajwt.dewi.red"
    ],

Testing

References

https://github.com/MicrosoftDocs/azure-docs/issues/5394

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/active-directory-claims-mapping.md

https://github.com/Azure-Samples/active-directory-dotnet-daemon-certificate-credential#create-a-self-signed-certificate

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/v2-protocols-oidc.md#fetch-the-openid-connect-metadata-document

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/active-directory-claims-mapping.md#example-create-and-assign-a-policy-to-include-the-employeeid-and-tenantcountry-as-claims-in-tokens-issued-to-a-service-principal

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!

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

KB

https://support.office.com/en-us/article/capabilities-of-built-in-mobile-device-management-for-office-365-a1da44e5-7475-4992-be91-9ccec25905b0?ui=en-US&rs=en-US&ad=US 

Background

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)

IntuneEnrollment


Service side settings

And in order for this setup to work

  • Block non-Office365 MDM supporting devices

OrgWide

  • 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

 

 

 

Demonstration – Illicit consent grant attack in Azure AD / Office 365

Disclaimer: All information contained within this post is common knowledge, given you grasp basic concepts of AAD default behavior,  OAuth2 Authorizations and how Javascript works in browsers.


 

How does it work?

Attacker creates multi-tenant app, and adds non-admin requiring API permissions for the desired API’s.  Attacker then delivers the link to end user, which after clicking the link only has to consent (Approve) the API permissions for the attacker.

Attacker has then access to user mail, this happens because attacker exfiltrates in the background the Access Token and uses it against the Exchange Online API

Down below I describe why it might be hard for the user to discern good app from the bad app.

Why it works?

Multi-tenants app by default have access to end users data, unless your Azure AD Admin have disabled the particular defaults that enable this behavior.

 

Exfil.jpg

Graph API + Other API’s

PoC

  • Create multi-tenant Azure AD Web-App/API + Azure Web App that delivers the JS. client side code to browser from Node.JS Web App
  • I am using minified ADAL.JS as <script src in the code. Other than that, I’ve just added few custom functions to the code, and studied wide variety of existing Vanilla ADAL JS Apps in GitHub
  • Figure out a way to deliver the link to the victim/victims in other AAD / Office 365 tenants
  • I’ve opted to forge existing Microsoft Newsletter with link to malicious Azure Web App to
  • When user clicks the link consent dialog is prompted.

JSA.PNG

  • Consent – This as any other dialog, you would get prompted for when consent framework kicks in (This is where the confidence part happens)

Newsletter.PNG

The process is same for benign apps, so there is superficially nothing to separate the good app from the bad (except caution, which is often unheeded, if you consider how often an mobile application presenting similar dialog is approved for multiple permissions in mobile phone)

  • User is redirected to the malicious app with seemingly benign OIDC-Flow (response_type=id_token)… no mention here of the OAuth2 Implicit Grant Flow (yet)

Redir

  • Hidden iFrame executes the OAuth2 Implicit Grant Flow, and gains Access Token to resource

Tokens

  • AJAX HTTP is used to post the Access Token as Payload to external exfil service
    • In case you wonder, that I’d left the function key there :)… Just try

JAX

  • User is redirected to the original link requested in the mail

OH.PNG

<- Users timeline stops here


 

– >Perpetrators timeline begins here

  • Token is copied by the malicious actor from Azure Functions where it was posted

Exfil

JWTIO

  • A Separate Powershell session running in some dark corner of the world is now searching users mail content for several keywords, and playing with the Access Token for the next 3600 seconds

Passwords

 

See it live:

Evil side:

User side

 

 

 

Microsoft References

https://docs.microsoft.com/en-us/office365/securitycompliance/detect-and-remediate-illicit-consent-grants 

https://blogs.technet.microsoft.com/office365security/defending-against-illicit-consent-grants/

Microsoft has issued multiple items in Secure Score controls about the scenario

Stay tuned for PT2 (How to defend against it)