More on entries from other tenants visible in the Unified Audit log

An year ago, I blogged about some suspicious events in the Office 365 Unified Audit log, that seemed to correspond to some “Unknown” principal. The culprit turned out to be users clicking on sample links to files/sites hosted in my tenant’s SPO instance, which I’ve posted on various blog sites and forum posts. Since then, Microsoft has made some changes to the way events are being presented in the Unified audit log, and those “unknown” entries seem gone for good.

Now, it’s time for another round of the same, this time with Exchange Online being the culprit. When doing my regular review of the Unified Audit log earlier this week, I spotted few entries that puzzled me – a user from a different tenant was being shown as successfully logged in, with no additional related events/actions. Even more interesting was the fact that said events were nowhere to be found in either the Azure AD sign-in logs or the CAS audit trail. Only the Unified Audit log had such events, and they were all linked to the id of the Exchange Online service principal, 00000002-0000-0ff1-ce00-000000000000. Here’s a sample event:

As with our previous SharePoint-related investigations, the next step I took was to look at the extended event information for those entries. As expected, the ActorContextId value did not match the one for my tenant, so this was again a case of someone trying to access a resources in a different tenant. The user IDs visible form such entries most definitely did not correspond to a Guest user in my tenant, so I figured this is just an “updated” view of all the “Unknown” entries I’ve dealt with previously. Surprisingly though, I wasn’t able to see any of the SPO related entries in the log.

More importantly, while I had some understanding on how a user can generate such entries for SharePoint Online, the case of Exchange Online was a bit more puzzling. Yes, we do have a web-based endpoint for Exchange Online and we can use it in order to replicate the SPO behavior. I can simply login to my tenant, then try to access a mailbox from a different tenant via direct link such as Such events have never been visible in the audit logs however, and a quick test on my end confirmed that this method indeed didn’t generate any new entries.

So I continued to look at the extended event information for additional clues, until I spotted the value of the ApplicationId, namely a0c73c16-a7e3-4564-9a95-2bdf47383716. You will not find this id listed anywhere in the Azure AD portal or PowerShell, however from my previous fiddlings with the ExO MFA PowerShell I was well aware that it corresponds to the “Microsoft Exchange Online Remote PowerShell” application. Knowing that, it was easy to reproduce the workflow that results in generating such requests. It was all due to users copy/pasting the PowerShell code samples from the above article, without changing the tenantID on the first line.

This in turn results in the user getting a token with incorrect issuer value, as shown below. While Azure AD issues such tokens in the first place is a different question, especially considering the scope (level of permissions) being issued as part of the token.

So, another mysterious entry in the Unified audit log got explained. As an added bonus, here’s also an explanation about some “random” events being generated from US-based IP addresses, such as the ones below:

All of these are events corresponding to running the PowerShell script sample for fetching events from the O365 Service Communications API, as detailed in this article. All the events were initiated from my home PC, located in Sofia, Bulgaria, and using a different IP address. Yet we only end up seeing the “proxied” requests in the log, bouncing off some server in Chicago. Not confusing at all, thanks Microsoft ­čÖé

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

PowerShell script to remove (reset) folder-level Exchange permissions, in bulk

Few weeks back, a question on the TechNet forums caught my attention and got me thinking of what’s the proper way to “reset” folder-level permissions in a mailbox. My initial thoughts on the subject resulted in the outlining of some building blocks that such a solution should address, and were published in this article. Now, I’m presenting you with a PowerShell script that should make the process easier.

As with almost all of the scripts I release, the intention is to automate things as much as possible while still exposing some parameters to exert additional control over the script execution. In this particular scenario, the intent is to strip any and all folder-level entries from a given mailbox, so the only parameter you need to provide is an identifier for said Mailbox. You can use any attribute that uniquely identifies the mailbox, such as SMTP address, UPN, GUID and so on. And, to make things more interesting, you can provide multiple entries and run the script against a group of mailboxes, all in one go. Here’s an example:

.\Reset_Folder_Permissions_recursive_BULK.ps1 -Mailbox (Get-Mailbox -RecipientTypeDetails RoomMailbox)

Invoking the script with the above set of parameters will cause it to enumerate all Room mailboxes in the organization, then for each mailbox go over the list of folders and remove any non-default permission entry. Folders will be enumerated via the Get-MailboxFolderStatistics cmdlet, which makes sure that any “known” folder type will be returned, regardless of the regional settings of the mailbox. In addition, any user-created folder will also be returned. Lastly, a bunch of “safe to ignore” folders is used to exclude entries that you shouldn’t care about. In case you want to make adjustments to the types of folders to include or exclude, edit the corresponding lists at line 6 and 11 of the script.

Few additional parameters are exposed, as follows:

  • ResetDefaultLevel – use this switch parameter when you want to also “reset” the Default entry for each mailbox. This will stamp each Calendar folder with the ‘AvailabilityOnly’ permission level for the Default principal, and the ‘None’ permission level for every other folder type.
  • Quiet – used to suppress script output to the console.
  • WhatIf – used to run the script in ‘preview’ mode, showing you which permissions will be removed, without actually making the changes. Very useful as troubleshooting tool and recommended as a first run experience.
  • Verbose – forces the script to spill out a bunch of additional output, useful for troubleshooting purposes.

The script can be run against Exchange on-premises or Exchange Online, and utilizes implicit remoting for both. This in turn allows better control over the amount of data flowing over the wire and should reduce the execution time in most scenarios. However, as numerous cmdlets are run for each mailbox (getting the mailbox, getting the folders, getting the permissions for each folder, removing the permissions for each folder), things can easily add up and you might end up hitting throttling limits. To help address such scenarios, small delays are added before processing each mailbox, which you can adjust as necessary (lines 122 and 139).

Because it supports various Exchange install types and because of the numerous connectivity methods available nowadays, the script does not include any logic to connect to Exchange Remote PowerShell. You will have to take care of this on your own, before invoking the script. The script however will detect any existing sessions and try to reuse them, where possible.

The script deliberately ignores any permissions entries for which the UserType value is Unknown or Anonymous. Only UserType values of Internal and External are removed, while the Default value is replaced (only when the –ResetDefaultLevel parameter is used). Lastly, the script will generate a CSV file with all the permission changes made, which you can find in the working directory.

For additional information or to download the script, head to the TechNet Gallery or GitHub.

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

New versions of the AzureAD ( and AzureADPreview PowerShell modules available

Over the weekend, new versions of the Azure AD PowerShell modules have been released over at the PowerShell Gallery. As usual, they don’t come with any release notes whatsoever, and as usual I run a quick comparison between the cmdlets in hopes to find out what exactly changed. Here are the details:

The Azure AD PowerShell module, bumped to version, now features 176 total cmdlets. Of these, 5 are new:

  • Get-AzureADMSIdentityProvider – used to view the settings of the different identity providers you have configured for your tenant. In case you have missed it, Microsoft announced Google federation a while back, and also supports Facebook, Amazon, or LinkedIn according to the documentation.
  • New-AzureADMSIdentityProvider – used to create a new identity provider
  • Remove-AzureADMSIdentityProvider – used to remove existing identity providers
  • Set-AzureADMSIdentityProvider – used to update the settings of existing identity providers
  • Get-CrossCloudVerificationCode – and undocumented cmdlet, which seems to correlate to a newly introduced method for domain verification. All my attempts to run the cmdlet have failed with a “Specified HTTP method is not allowed for the request target.” message.

Which brings us to the list of changed/updated cmdlets:

Confirm-AzureADDomain now features a CrossCloudVerificationCode parameter, supposedly accepting the code generated with the Get-CrossCloudVerificationCode cmdlet. Cross-cloud here most likely means cross-Office365-instance, for example when you are trying to move between 21Vianet and the multi-geo instance?

Lastly, two additional parameters have been introduced to the New-/Set-AzureADApplicationProxyApplication cmdlets: IsPersistentCookieEnabled and IsSecureCookieEnabled.

For the AzureADPreview module, which has reached version, the number of cmdlets has reached 210. Of these, 3 are new:

  • Get-AzureADApplicationSignInSummary – a nice new addition that lists all Azure AD integrated applications along with the number of successful and failed sign-ins for the past 7 or 30 days. Here’s an example:
  • Get-AzureADApplicationSignInDetailedSummary – similar to the above, however it returns trending information over the past 30 days or so. Thus you can expect to see multiple entries per application. Here’s an example:

    In addition, the Status property will contain information about the last failure event details, current at that time.
Get-AzureADApplicationSignInDetailedSummary | ? {$_.Status.ErrorCode}
  • Get-CrossCloudVerificationCode – which we already covered above, so it doesn’t count.

With regards to updated cmdlets, only the Confirm-AzureADDomain one has received some attention, with the CrossCloudVerificationCode parameter added (discussed above).

Posted in Azure AD, Office 365 | 2 Comments

How to reset mailbox folder permissions

A thread over at the TechNet forums got me thinking about what is the best (or at least a proper) way to “reset” folder level permissions, with the added challenge of doing it in bulk. Generally speaking, it’s a simple operation, at most you’d have some loop running Remove-MailboxFolderPermission on each entry. But, there are some intricacies, so let’s dig in.

First of all, if you simply want to “reset” the permissions on a given, “known” folder, the task is easy. Say we have the user JohnSmith and we want to remove any permissions on his Calendar folder. All we need to do in such scenario is run the following cmdlet:

Get-MailboxFolderPermission JohnSmith:\Calendar  | % { Remove-MailboxFolderPermission -Identity $_.Identity -User $_.User }

Right? Wrong. There are actually many issues with the one-liner above, starting from the fact that we are not using the proper User identifier. As we’ve discussed in other articles, the Get-MailboxFolderPermission cmdlet returns the reduced recipient object, and not a string value. Thus, a correct entry to use would look something like: $_.User.ADRecipient.ExchangeObjectId.Guid.

Next, we need to exclude the “default” permissions entries, as in the ones configured for the Default and Anonymous security principals. This part is easy to handle with a simple Where clause, and we can even use regex to address some other cases (more on this later). And, we might also want to avoid having to confirm the removal of each individual entry, so the updated cmdlet will look something like this:

Get-MailboxFolderPermission JohnSmith:\Calendar `
  | ? {$_.User -notmatch "^(Default|Anonymous)$"} `
  | % { Remove-MailboxFolderPermission -Identity $_.Identity -User $_.User.ADRecipient.ExchangeObjectId.Guid -Confirm:$false }

From here, we can generalize this cmdlet to run against multiple mailboxes and achieve our dream of resetting the permissions in bulk. Something like this should do the trick:

Get-Mailbox -RecipientTypeDetails RoomMailbox `
  | % { Get-MailboxFolderPermission "$($_.PrimarySmtpAddress):\Calendar" } `
  | ? {$_.User -notmatch "^(Default|Anonymous)$"} `
  | % { Remove-MailboxFolderPermission -Identity $_.Identity -User $_.User.ADRecipient.ExchangeObjectId.Guid -Confirm:$false }

Now that’s a loooong one-liner, but frankly we are still just getting started. There are many additional factors that we need to address, such as the actual folder names, as depending on the localization, the Calendar folder might be renamed to Kalender or whatnot. Then, what if we want to include all folders in the mailbox, not just Calendar? And there are things to consider when removing the permissions as well, such as dealing with orphaned entries, external permissions, published Calendars. Are you getting bored yet?

What started as a simple exercise will have to be turned into a full-blown script if we want to handle everything correctly. Which of course is true for most examples – there is no way to properly handle errors or account for throttling in one-liner solutions. I will have to leave the complete solution for another post, but here are some of the building blogs we need to put together:

  • Account for the type of User, and depending on it handle things accordingly. In other words, for each permission entry, look at the $entry.User.UserType.Value. Available values will include Internal, External and Unknown and all of these will have to be handled differently.
  • Utilize the Get-MailboxFolderStatistics cmdlet to get a┬álist of┬áthe localized folder names and┬átrim the list┬áto only include folders you care about. There’s no point in adjusting permissions on Purges folder for example.
  • If you are using the above method to get the localized folder names across multiple mailboxes, you need to start to account for throttling!
  • Decide what you want to do with the Default (and Anonymous) permission level. The regex we used in the above example can be generalized to exclude other entries as well, if needed.
  • Put some logging or utilize the –WhatIf parameter to “preview” the result.

The script I published on the TechNet Gallery a while back can help you get started with all this. And I’ll get started on actually turning all of the above into a proper script. In the meantime, those nifty one-liners should do.

P. S. Things are so much easier with Mailbox permissions, as we discussed previously:

Remove-MailboxPermission JoshSmith -ResetDefault
Posted in Exchange Online, Office 365, PowerShell | 1 Comment

An oddity with displaying permissions entries for Azure AD integrated applications

Few days back, while trying to answer a question about a consent prompt for some application, I opened the Azure AD blade and noticed something puzzling. I don’t remember if this was a deliberate choice or by chance, but I clicked on the “MS Tech Comm” application, the one enabling us to login with Office 365 accounts to the Microsoft Tech Community. On the Permissions tab for the app, under User Consent, an empty entry was visible. By empty, I mean this:

Note the lack of Permission display name and Permission description values, pretty important ones if you ask me. And it turns out this isn’t a single/random occurrence, I was able to reproduce the exact same behavior by using a new tenant to access the MTC and provisioning the app. Moreover, other applications in my tenant seem to show the same trait, prime example being the Graph explorer application!

To try and understand whether this is a cosmetic issue or something more serious, I started looking into all the different methods we have to get the list of permissions granted to a given application. The good old MSOnline module can list the corresponding service principal object, but doesn’t have any method to display permissions. With the Azure AD module on the other hand we can leverage the Get-AzureADServicePrincipalOAuth2PermissionGrant cmdlet to get the permissions. I’ve even published a script that can help you enumerate all third party apps and their corresponding permissions. Here’s what I could get out of the Azure AD module in this case:

Scope :  User.Read openid email profile offline_access

At first glance, nothing interesting. The subtle difference is the presence of another leading space character, right after the semicolon character. Since space is the separator used when returning permissions entries, this indicates that another, “empty” permission entry is present. In contrast, here’s how the Scope output would look for service principal that doesn’t have such entries:

Scope : Directory.Read.All User.Read

It becomes a bit easier to understand if you use the Select -ExpandProperty method in PowerShell or get the property directly:

PS C:\> (Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId 2447dc5e-44d0-46b2-9d0e-cd5572ea4ca2).Scope
 User.Read openid email profile offline_access

PS C:\> (Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId f842c430-48bb-44d7-a67a-c0f60ce7d5f4).Scope
Directory.Read.All User.Read

In order to exclude the possibility that this is simply an oddity of the PowerShell representation of the permission object, I went and checked it with the Graph API directly. Here’s the result in JSON form:

    "@odata.context": "$metadata#oauth2PermissionGrants",
    "value": [
            "clientId": "5c5ee9b1-5343-434d-b683-8213a4d4267c",
            "consentType": "Principal",
            "expiryTime": "2019-09-28T17:34:31.6578813Z",
            "id": "seleXENTTUO2g4ITpNQmfJrNnWjE96ZNgP_OmB9amDbX3p6kjBWNTL7nhv-T0LWk",
            "principalId": "a49eded7-158c-4c8d-bee7-86ff93d0b5a4",
            "resourceId": "689dcd9a-f7c4-4da6-80ff-ce981f5a9836",
            "scope": " User.Read openid email profile offline_access",
            "startTime": "0001-01-01T00:00:00Z"

Leading space again, so I’m not being crazy here. For comparison, another app representation via the Graph:

    "@odata.context": "$metadata#oauth2PermissionGrants",
    "value": [
            "clientId": "d6828122-1a97-46d8-be20-a72d7bf74604",
            "consentType": "Principal",
            "expiryTime": "2019-07-11T20:15:09.1639414Z",
            "id": "IoGC1pca2Ea-IKcte_dGBIm2UOKu5f1NjPfelFES7OLh7ZAh7hDjQq9EXxSU0hSV",
            "principalId": "2190ede1-10ee-42e3-af44-5f1494d21495",
            "resourceId": "e250b689-e5ae-4dfd-8cf7-de945112ece2",
            "scope": "User.Read Group.ReadWrite.All",
            "startTime": "0001-01-01T00:00:00Z"

As already shown above, the Azure AD portal also treats this as an additional entry. On the other hand, the Cloud App Security portal seems to trim the leading space and only reports 5 permission entries (as opposed to the 6 entries visible in the Azure AD blade). The Azure AD Audit logs also seem to trim the leading space, so the mistery remains. The application consent screen itself only lists the default OIDC v2.0 scopes:

A quick checkup against the rest of the Azure AD integrated applications in my tenant shows few other entries that display the same behavior. And just to make sure that this is not yet another oddity specific to my tenant, I run it against few more, including a demo tenant. Interestingly enough, all of apps showing this behavior are authored by Microsoft. Here’s how to generate a list of such applications:

PS C:> Get-AzureADOAuth2PermissionGrant -All $true | ? {$_.Scope.StartsWith(" ")}

Application    ConsentType User              Scope                                            
-----------    ----------- ----              -----                                            
MS Tech Comm   Principal  User.Read openid email profile offline_access   
FastTrack      Principal  profile openid                                  
Graph explorer Principal  openid profile User.ReadWrite User.ReadBasic.All ...
OAuth Sandbox  Principal  openid offline_access profile Mail.ReadWrite Mail.ReadWrite.Shared ...

I’m not inclined to believe that Microsoft somehow allowed a “blank” scope, with no description or display name, so this is probably some minor code error causing a valid permission entry (or perhaps some “internal” one?) to be replaced by empty string, without removing the separator as well. Like in this example:

PS C:\> "a b c d"
a b c d

PS C:\> "a b c d".Replace("a","")
 b c d

On the other hand, it might be something more serious, so I’ve probed some folks about it. Whatever this turns out to be, it would be nice if we had consistent display across all admin endpoints.

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