Azure AD/ Entra ID apps : Restrict email permissions to specific mailboxes

There are scenarios when a datacenter hosted app or a cloud hosted app needs access to one or more Exchange Online mailbox.
In such cases, typically an Azure AD app is created with permissions to read/write access to mailboxes/calendars and contacts.
Issue here is by default the access is provided for ALL the mailboxes. If an attacker gets holds of the app, the could potentially access emails from sensitive mailboxes and exfilter them.

The setup

The Azure AD app with mail.read/mail.send permissions.
The credential (secret) has been created for this app and used by a service app named “service1” .
The service1 app will read email from the mailbox service1.mailbox@redteamsimulation.com.

However, one can make use of the credentials for this Azure AD app to get emails from not only originally intended mailbox for the service but also sensitive mailboxes such as those of CEO and CFO as you can see in the below screenshot.

Code to get emails from all the mailboxes

Prerequisites : Install and import ExchangeOnlinemanagement module and Microsoft.Graph modules

Install-Module ExchangeOnlineManagement
Import-Module ExchangeOnlineManagement
Install-Module Microsoft.Graph
Import-Module Microsoft.Graph
# Import the required module
Import-Module Microsoft.Graph
$err_string= ''
# Set the necessary variables
$clientId = "7477abb4-xxxx-xxxx-xxxx-xxxxxx"
$tenantId = "c2b84b0b-xxxx-xxxx-xxxx-xxxxxxx"
$ClientSecretCredential = Get-Credential -Credential $clientId

# Connect to Microsoft Graph
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $ClientSecretCredential -NoWelcome

# Get all users in the tenant
$users = Get-MgUser

# Loop through each user
foreach ($user in $users) {
	# Get the user's mailbox
	try {
		$mailbox = Get-MgUserMailFolderMessage -UserId $user.Id -MailFolderId 'Inbox' -ErrorAction Stop
		$test = $user.Mail
		write-host "####### Reading emails for mailbox " -nonewline
		write-host $test -foreground red -nonewline
		write-host " ##########" 
		write-host "Found " -nonewline
		write-host $mailbox.Length -foreground red -nonewline
		write-host " email(s) " 
		foreach ($message in $mailbox) {
			# Print the message subject and received date
			Write-Output (" ----------------------------------------------------")
			Write-Output ("Subject: " + $message.Subject)
			Write-Output ("Received: " + $message.ReceivedDateTime)
			$body = $message.Body.Content -replace '<[^>]+>',''
			$body = $body.trim()
			Write-Output ("Body: " + $body)
		}
	write-host "`n"
	}
	catch
	{ 
		$err_string = $_ | Out-String
	}
	if ($err_string -inotmatch "The mailbox is either inactive, soft-deleted, or is hosted on-premise")
	{
		Write-Host $err_string
	}
}
# Disconnect from Microsoft Graph
Disconnect-MgGraph

Limiting access to only certain mailboxes

Below powershell will :
a) Create a mail-enabled security group with the mailbox we want to only allow to be accessed from the app.
b) Create an application access policy for the app with access restricted to only the mail enabled group created in step a)

$MailEnabledDistGroup=New-DistributionGroup -Name "Service1-RestrictedDistGroup" -Type "Security" -Members "service1.mailbox@redteamsimulation.com"
New-ApplicationAccessPolicy -AppId <AppId> -PolicyScopeGroupId $MailEnabledDistGroup.Id -AccessRight RestrictAccess -Description "Mailbox restrictions"

In my tests, the application access policy took effect in 60-90 minutes and after that accessing other mailboxes would give an error.
Below is the output running the same script as above.