Additional Azure AD app management policy controls introduced

In a previous article, we introduced the concept of Azure AD application management policies, a configuration object that allows tenants to control the type and lifetime of credentials used across Azure AD integrated applications and service principals. Said article was supposed to be published a month back, when the feature was released in preview, but due to some unforeseen delays lingered in limbo for a while. In the meantime, Microsoft has released some new additions to the app management policy controls, so let’s go over them.

First, the policies now support key credential (certificate) restrictions. You could actually see a hint about this in the default tenant policy output, which returns two sections: passwordCredentials and keyCredentials. For the time being, the only restrictions supported for keyCredentials is the lifetime of the certificate, configurable via the asymmetricKeyLifetime property. The example below shows how you can set such restrictions in the default tenant-wide policy:

Keep in mind that the example above is effectively overwriting the existing configuration. If you have already set restrictions for passwordCredentials, make sure to add them to the PATCH request. Here’s how the updated default policy settings look like:

And how do these restrictions affect the app registration/SP credentials, you ask? Basically, it restricts the maximum lifetime of a certificate credential, as in any certificate you want to assign to a given app/sp object must have validity equal to or less than the value specified in the asymmetricKeyLifetime property. Otherwise, you will be greeted by the following error message:

Isn’t that just beautiful? In case you are wondering, the GUID visible in the error message above is the one for the default policy, namely 4f9428ab-d614-4792-8e04-d3dfd3c7fd40.

Moving on to the next set of restrictions. Those include symmetricKeyAddition and symmetricKeyLifetime, both configurable within the passwordCredentials section (not sure why?). Generally speaking, symmetricKeys is not something you should be using, even though configuring them is still possible via PowerShell or by manually editing the manifest file. Since they are much less secure than using assymmetric keys (public/private key pair), being able to prevent their creation is a welcome addition. To do this, add the corresponding elements within the passwordCredentials section. If you want to block the use of symmetric keys altogether, set symmetricKeyAddition restriction, and if you want to limit their lifetime, set symmetricKeyLifetime, or configure both. Here’s an example:

The above restrictions were applied on both application and service principal objects, as part of the default tenant-wide policy. In effect, they should be enforced on every new application or service principal object created after August 1st, 2021. To test whether the restrictions are in effect, we can use the Graph API endpoints, or try to add a key manually via the app manifest. In both cases, we should be prevented from completing this successfully, with an error message similar to the previous scenario:

Do note that the restrictions do not apply to the older Azure AD API endpoints, including the good old New-MsolServicePrincipalCredential cmdlet and it’s sibling from the Azure AD PowerShell module, New-AzureADServicePrincipalKeyCredential.

Overall, these new restrictions were more or less what we expected to be delivered before the app management policies feature goes GA. I’m still having some trouble with getting the service principal restrictions work as expected, or at least as I would expect them to work – preventing third-party applications from actually being added to the tenant, in case they don’t comply with the policy. Once I figure this out, I’ll update here 🙂

Posted in Azure AD, Graph API, Microsoft 365, Office 365 | Leave a comment

Script to remove mailbox permissions for a user, in bulk

I’ve seen few request for addressing the “remove all permissions a given user has across mailboxes” scenario, so I decided to give it a go. Here we’re talking about mailbox level permissions, and specifically Full Access. A script like that can be useful in scenarios where a given user has been compromised, or is leaving the company, or you simply want to clean up old/unused permissions.

The first issue to solve here is gathering an inventory of all the permissions a user had been granted. To date, there is no easy way to do this in Exchange or its cloud sibling, and we’re effectively left with having to iterate over each mailbox in the organization. For a single user (and a small number of mailboxes), this is relatively simple, as discussed in one of my most popular articles. When you have to perform the same task against a large number of users and have to account for throttling though, using the pipeline method is a no go, thus I’ve also released a full-blown script to gather mailbox permission inventory.

Once we get the list of mailboxes over which a given user has been granted Full Access permissions, the rest of the script is just a simple loop, executing the Remove-MailboxPermission cmdlet as necessary. Some additional plumbing has been added to address throttling, basic error handling and quality of life, including the ability to provide a list of users against which to run the script. As with most other “destructive” cmdlets, I’ve made an effort to support the –WhatIf switch in order to preview the results before actually applying them. Using –Verbose will make the script spill out additional information on every step. Finally, some basic “report” will also be provided.

As usual, I’ve made no attempt to include a comprehensive “connect to Exchange” function, due to the multitude of methods currently available. If an existing remote PowerShell session is detected, it will be reused. If not, the script will try to connect to Exchange Online via the basic authentication method, requiring you to provide credentials in the process. To avoid issues with this, and account for your specific needs, either connect to Exchange Remote PowerShell before running the script or add the corresponding function in code. On the bright side, the script can run against Exchange Server too, as there are no dependencies on Exchange Online connectivity.

Apart form the “check connectivity” function, the script has one other function, namely Remove-UserMBPermissions, which does all the heavy lifting. To provide some flexibility, the –Identity (aliased as “UserToRemove”) parameter supports a comma-separated list of users and also takes pipeline input. Any entries you provide will be checked against the Get-User cmdlet to make sure a valid, unique security principal can be found before running the removal cmdlet.

If a single entry is provided, for the sake of simplicity the script will use the “stupid” pipeline-based method as detailed here. While this is an acceptable approach for a one-off operation, this method is not practical for large environments. To see why, you can use the –Verbose or –WhatIf switch – the output will illustrate that the Remove-MailboxPermission cmdlet is run against each and every mailbox within the organization, regardless of whether the user actually has permissions on them.

A better approach, one based on obtaining the full list of Full Access permissions within the organization before doing the removal, will be used if you’ve provided more than 5 users. This in turns makes the script dependent on the Mailbox permissions inventory script, which you can get from GitHub. After downloading, make sure to place the Mailbox_Permissions_inventory.ps1 script in the same directory as the current one. Once you do that, the script will do a loop over just the mailboxes that actually have Full Access permission entries for a given user, which will help avoid throttling issues and should speed up the script execution in at least some scenarios.

If for some reason we’re not able to obtain the Full access permissions inventory, the “stupid” method will be used instead, so the script should still work. If you want to always use the “proper” method or change the number of values for which it will be triggered, adjust the relevant setting in the if statement of line 109.

In addition, two other parameters are added in case you want to cover Shared (-IncludeSharedMailboxes) and room/equipment (-IncludeResourceMailboxes) mailboxes respectively. Lastly, the function supports the –Verbose and –WhatIf parameters as mentioned above. All the parameters can be passed directly to the script as well.

By default, the script will run against all user mailboxes and remove any Full Access permission entries for the user(s) you provided:

.\Remove_user_MBpermissions.ps1 -Identity vasil

To do the same for a set of users, use the following syntax:

.\Remove_user_MBpermissions.ps1 -Identity AdeleV,IsaiahL,DebraB,PradeepG

To cover all mailbox types and preview the changes before committing:

.\Remove_user_MBpermissions.ps1 -Identity AdeleV,IsaiahL,DebraB,PradeepG,admin -IncludeSharedMailboxes -IncludeResourceMailboxes -WhatIf

Lastly, the script will also spill out a CSV file, detailing the permissions that were removed, which you can find in the working directory.

Without further ado, you can find the script over at GitHub. Here’s also the relevant help file. You can refer to it or the built-in script help for some examples on how to run it and description of the relevant parameters.

Before I forget, the script should also run fine on-premises, though I only did a handful of tests against my Exchange 2019 box, so no guarantees.

It’s also worth mentioning some other scripts that might be relevant. If you want to address the “remove folder permissions” scenario as well, refer to the script detailed in this article. Similarly, this article addresses the scenario where you want to ‘clear’ user’s membership, as in remove him from all groups within Office 365.

Posted in Exchange Online, Microsoft 365, Office 365, PowerShell | Leave a comment

Querying the B2BManagementPolicy settings via (legacy) Graph API

Here’s another one in the series “I blog so I don’t forget about this”, this time related to the B2BManagementPolicy in Azure AD. For whatever reason, Microsoft is still to provide us with a Graph API endpoints for this, and searching through the official (and /beta) documentation will not yield any results. Turns out however, there is a way to query those settings, as shared by a Microsoft employee over at Q&A.

To get the B2BManagementPolicy policy settings, one needs to query the (hidden) /legacy/policies endpoint. If no such policy has ever been configured in your tenant, either via the Azure AD blade or the Get-AzureADPolicy PowerShell cmdlet, the result will be empty. If however you have configured the policy, you will get reply similar to the below:

Which you can easily compare with the values from the Azure AD blade or PowerShell:

# Get-AzureADPolicy |fl

Id : c9755042-7b11-4504-b9e6-6347d0beabf5
OdataType :
AlternativeIdentifier :
Definition : {{"B2BManagementPolicy":{"InvitationsAllowedAndBlockedDomainsPolicy":{"BlockedDomains":[""]},"PreviewPolicy":{"Features":["OneTimePasscode"]},"AutoRe
DisplayName : B2BManagementPolicy
IsOrganizationDefault : True
KeyCredentials : {}
Type : B2BManagementPolicy

Few things need to be mentioned here. First and foremost, not only this is under /beta, but its also under an unpublished /legacy endpoint, so it goes without saying you should not be using this for your production workloads. You can also create/modify existing policies using POST/PATCH requests, as long as you have granted the necessary permissions first. And if you are having any troubles with this, you can also use the old Azure AD Graph method as detailed in this GitHub issue.

So there you have it, a method to query or update B2BManagementPolicy settings in a totally unsupported way, until Microsoft provides proper Graph API endpoints to do the same in a supported manner.

Posted in Azure AD, Graph API, Microsoft 365, Office 365 | Leave a comment

Additional data exposed for Email detections events in the Unified audit log

As part of Roadmap item #70744, Microsoft has enriched the Email detections event data ingested to the Unified audit log with additional details. Those include the following new properties:

  • AdditionalActionsAndResults – The additional actions that were taken on the email, if any. Examples include ZAP or Manual Remediation.
  • Connectors – The list of connectors associated with the message, if any (names and GUIDs included).
  • AuthDetails – The authentication checks that are done for the message. These include the results for SPF, DKIM, DMARC, and CompAuth checks, see example below.
  • SystemOverrides – Overrides that are applicable to the message. These include functionalities such as transport rules or safe sender lists, and can be tenant- or user- configured. The property will also list the result for each override, as well as the FinalOverride in the case where multiple overrides affected the message.
  • Phish Confidence Level – Indicates the confidence level associated with Phish verdict. It can be Normal or High.

To take a look at the new properties, one can use the Search-UnifiedAuditLog to query the Unified audit log for any ThreatIntelligence events, or just use the RecordType value of 28 (see table below). The following example will gather all such events in my tenant for the past 90 days:

$events = Search-UnifiedAuditLog -EndDate (Get-Date) -StartDate (Get-Date).AddDays(-90) -RecordType ThreatIntelligence

As with other event types, the bulk of the important data is stored within the AuditData property, in JSON format. Use the following to parse it:

$events[0].AuditData | ConvertFrom-Json

The result will be a list of all properties supported for the Email message event type within the Management activity API schema. Some of the properties can be further expanded to expose additional details. For example, here is what the AuthDetails property holds for the selected message:

($events[0].AuditData | ConvertFrom-Json).AuthDetails

Name Value
---- -----
SPF Pass
Comp Auth fail

Similarly, we can explore the other newly added properties. Below is an example of a message where the EOP verdict was overwritten by a personal safe list entry:

 ($events[2].AuditData | ConvertFrom-Json).SystemOverrides
Details FinalOverride Result Source
------- ------------- ------ ------
Sender address list Yes Allow User

In a nutshell, some new properties are now being exposed in the audit events, and that’s a good thing. Combined with the data already available, one can now get a better understanding of what the event signaled, and if needed export this data to external SIEM.

For the sake of completeness, one can now also find Submission events as well as Automated investigation and Response (AIR) events. Here’s also a list of RecordType values you can use for each:

Value Member name Description
28 ThreatIntelligence Phishing and malware events from Exchange Online Protection and Microsoft Defender for Office 365.
29 MailSubmission Submission events from Exchange Online Protection and Microsoft Defender for Office 365.
41 ThreatIntelligenceUrl Safe Links time-of-block and block override events from Microsoft Defender for Office 365.
47 ThreatIntelligenceAtpContent Phishing and malware events for files in SharePoint Online, OneDrive for Business, and Microsoft Teams, from Microsoft Defender for Office 365.
64 AirInvestigation Automated investigation and response events, such as investigation details and relevant artifacts, from Microsoft Defender for Office 365 Plan 2.


Posted in Exchange Online, Microsoft 365, Office 365 | Leave a comment

Graph API introduces a transitiveReports endpoint for users and contacts

After a long overdue vacation, I took a look at the Graph API changelog in order to catch up with any new developments. One of the things that caught my eye was the new transitiveReports endpoint, which should nicely complement the already available directReports and manager endpoints. The latter in particular can be used together with the $expand and $levels operators to provide a chain list of managers, or in other words a “transitive” list. Now, thanks to the transitiveReports endpoint, we can do the same in the opposite direction. Well, eventually.

Since this endpoint is only available in /beta currently, it’s functionality is a bit limited. In fact, all you can do currently is get the number of direct reports, and not the actual objects themselves. In other words, currently the only working query is one that includes the $count operator, in addition to the required ConsistencyLevel=’eventual’ header. Here’s an example query:$count

and how it looks like inside Graph explorer:

While this might seem rather useless for the time being, my hope here is that we will eventually be able to run a similar query to get the full list of reports, hopefully by also specifying a level value, similarly to how the manager endpoint works. This in turn will allow for easy just-in-time querying of managerial relationships within the organization, without having to run repeated queries for each individual up/down the chain.

For additional information on the endpoint as well as any future updates, keep an eye on the official documentation.

Posted in Azure AD, Graph API | Leave a comment