Skip to content

Commit 54a7759

Browse files
authored
SecOps - 41080 - Conditional Access App Control session policies are enforced for sensitive cloud apps (#1481)
2 parents bcd41bb + 0ca2b48 commit 54a7759

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
A stolen session token can let a threat actor access a sensitive cloud app from an unmanaged device without triggering a new sign-in. Conditional Access App Control routes the session through Microsoft Defender for Cloud Apps, where session policies can monitor activity, block downloads or copying, require step-up authentication, and apply sensitivity labels. This check confirms that at least one enabled Conditional Access policy routes targeted cloud apps through Defender for Cloud Apps session control.
2+
3+
## Remediation resources
4+
5+
- [Protect apps with Microsoft Defender for Cloud Apps Conditional Access App Control](https://learn.microsoft.com/en-us/defender-cloud-apps/proxy-intro-aad)
6+
- [Deploy Conditional Access App Control for featured apps](https://learn.microsoft.com/en-us/defender-cloud-apps/proxy-deployment-aad)
7+
- [Create access and session policies](https://learn.microsoft.com/en-us/defender-cloud-apps/session-policy-aad)
8+
- [Conditional Access policy resource type](https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy)
9+
10+
<!--- Results --->
11+
%TestResult%
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<#
2+
.SYNOPSIS
3+
Checks that Conditional Access App Control session policies are enabled for cloud apps.
4+
5+
.NOTES
6+
Test ID: 41080
7+
Workshop Task: SECOPS-080
8+
Pillar: SecOps
9+
Category: Identity threat protection
10+
Required permission: Policy.Read.All
11+
#>
12+
13+
function Test-Assessment-41080 {
14+
15+
[ZtTest(
16+
Category = 'Identity threat protection',
17+
CompatibleLicense = ('ADALLOM_S_STANDALONE&AAD_PREMIUM'),
18+
ImplementationCost = 'Medium',
19+
Pillar = 'SecOps',
20+
RiskLevel = 'High',
21+
Service = ('Graph'),
22+
SfiPillar = 'Protect identities and secrets',
23+
TenantType = ('Workforce'),
24+
TestId = 41080,
25+
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps',
26+
UserImpact = 'Medium'
27+
)]
28+
[CmdletBinding()]
29+
param()
30+
31+
#region Data Collection
32+
Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
33+
$activity = 'Checking Conditional Access App Control session policies'
34+
Write-ZtProgress -Activity $activity -Status 'Querying enabled Conditional Access policies'
35+
36+
try {
37+
$enabledPolicies = Invoke-ZtGraphRequest -RelativeUri 'identity/conditionalAccess/policies' -ApiVersion beta -Filter "state eq 'enabled'" -Select 'id,displayName,state,conditions,sessionControls' -ErrorAction Stop
38+
}
39+
catch {
40+
$httpStatus = Get-ZtHttpStatusCode -ErrorRecord $_
41+
Write-PSFMessage "Failed to retrieve Conditional Access policies (HTTP $httpStatus): $_" -Tag Test -Level Warning
42+
43+
$resultMessage = if ($httpStatus -in @(401, 403)) {
44+
'⚠️ The Conditional Access policy collection could not be retrieved because the assessment account lacks Policy.Read.All permission. Grant the permission and re-run the assessment.'
45+
}
46+
else {
47+
'⚠️ The Conditional Access policy collection could not be retrieved because Microsoft Graph returned a transient or unexpected error. Verify connectivity and re-run the assessment.'
48+
}
49+
50+
$params = @{
51+
TestId = '41080'
52+
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps'
53+
Status = $false
54+
Result = $resultMessage
55+
CustomStatus = 'Investigate'
56+
}
57+
Add-ZtTestResultDetail @params
58+
return
59+
}
60+
#endregion Data Collection
61+
62+
#region Assessment Logic
63+
$enabledPolicies = @($enabledPolicies)
64+
65+
$policyResults = foreach ($policy in $enabledPolicies) {
66+
$targetApps = @($policy.conditions.applications.includeApplications | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
67+
$sessionControl = $policy.sessionControls.cloudAppSecurity
68+
$isEnabled = $null -ne $sessionControl -and $sessionControl.isEnabled -eq $true
69+
$isMatchingPolicy = $isEnabled -and $targetApps.Count -gt 0
70+
71+
$knownTargets = @($targetApps | Where-Object { $_ -in @('All', 'Office365', 'MicrosoftAdminPortals') } | ForEach-Object {
72+
switch ($_) {
73+
'All' { 'All cloud apps' }
74+
'Office365' { 'Office 365' }
75+
'MicrosoftAdminPortals' { 'Microsoft admin portals' }
76+
}
77+
})
78+
$selectedTargetCount = @($targetApps | Where-Object { $_ -notin @('All', 'Office365', 'MicrosoftAdminPortals') }).Count
79+
if ($selectedTargetCount -gt 0) {
80+
$selectedTargetLabel = if ($selectedTargetCount -eq 1) { '1 selected app' } else { "$selectedTargetCount selected apps" }
81+
$knownTargets += $selectedTargetLabel
82+
}
83+
84+
$cloudAppSecurityType = if ($sessionControl.cloudAppSecurityType) { $sessionControl.cloudAppSecurityType } else { 'Not configured' }
85+
$rowStatus = if (-not $isMatchingPolicy) {
86+
'Fail'
87+
}
88+
elseif ($cloudAppSecurityType -eq 'unknownFutureValue') {
89+
'Investigate'
90+
}
91+
else {
92+
'Pass'
93+
}
94+
95+
[PSCustomObject]@{
96+
PolicyDisplayName = $policy.displayName
97+
PolicyId = $policy.id
98+
State = $policy.state
99+
TargetApps = if ($knownTargets.Count -gt 0) { $knownTargets -join ', ' } else { 'None' }
100+
CloudAppSecurityType = $cloudAppSecurityType
101+
IsEnabled = $isEnabled
102+
Matches = $isMatchingPolicy
103+
RowStatus = $rowStatus
104+
}
105+
}
106+
107+
$policyResults = @($policyResults)
108+
$matchingPolicies = @($policyResults | Where-Object Matches)
109+
$knownMatchingPolicies = @($matchingPolicies | Where-Object CloudAppSecurityType -ne 'unknownFutureValue')
110+
111+
$passed = $false
112+
$customStatus = $null
113+
if ($knownMatchingPolicies.Count -gt 0) {
114+
$passed = $true
115+
$testResultMarkdown = "✅ At least one enabled Conditional Access policy enforces Microsoft Defender for Cloud Apps session control via Conditional Access App Control.`n`n%TestResult%"
116+
}
117+
elseif ($matchingPolicies.Count -gt 0) {
118+
$customStatus = 'Investigate'
119+
$testResultMarkdown = "⚠️ Conditional Access App Control is enabled, but every matching policy returned an unknown Cloud App Security type. Review the policies in Microsoft Entra.`n`n%TestResult%"
120+
}
121+
else {
122+
$testResultMarkdown = "❌ No enabled Conditional Access policy has Defender for Cloud Apps session control enabled for a cloud app.`n`n%TestResult%"
123+
}
124+
#endregion Assessment Logic
125+
126+
#region Report Generation
127+
$portalUrl = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade'
128+
$policyUrlTemplate = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/{0}'
129+
$maxDisplay = 10
130+
$displayPolicies = @($matchingPolicies | Sort-Object -Property PolicyDisplayName | Select-Object -First $maxDisplay)
131+
132+
$tableRows = ''
133+
foreach ($policy in $displayPolicies) {
134+
$policyName = "[$(Get-SafeMarkdown -Text $policy.PolicyDisplayName)]($($policyUrlTemplate -f $policy.PolicyId))"
135+
$targetApps = Get-SafeMarkdown -Text $policy.TargetApps
136+
$securityType = Get-SafeMarkdown -Text $policy.CloudAppSecurityType
137+
$isEnabled = if ($policy.IsEnabled) { 'True' } else { 'False' }
138+
$status = switch ($policy.RowStatus) {
139+
'Pass' { '✅ Pass' }
140+
'Fail' { '❌ Fail' }
141+
'Investigate' { '⚠️ Investigate' }
142+
}
143+
$tableRows += "| $policyName | $($policy.State) | $targetApps | $securityType | $isEnabled | $status |`n"
144+
}
145+
146+
if ($displayPolicies.Count -eq 0) {
147+
$tableRows = "| No matching policies found | — | — | — | — | ❌ Fail |`n"
148+
}
149+
elseif ($matchingPolicies.Count -gt $maxDisplay) {
150+
$remaining = $matchingPolicies.Count - $maxDisplay
151+
$tableRows += "`n... and $remaining more. [Microsoft Entra > Conditional Access > Policies]($portalUrl)`n"
152+
}
153+
154+
$formatTemplate = @'
155+
156+
157+
## [Microsoft Entra > Conditional Access > Policies]({0})
158+
159+
| Policy display name | State | Target apps | Cloud App Security type | Is enabled | Status |
160+
| :------------------ | :---- | :---------- | :---------------------- | :--------- | :----- |
161+
{1}
162+
'@
163+
164+
$mdInfo = $formatTemplate -f $portalUrl, $tableRows
165+
$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
166+
#endregion Report Generation
167+
168+
$params = @{
169+
TestId = '41080'
170+
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps'
171+
Status = $passed
172+
Result = $testResultMarkdown
173+
}
174+
if ($customStatus) {
175+
$params.CustomStatus = $customStatus
176+
}
177+
Add-ZtTestResultDetail @params
178+
}

0 commit comments

Comments
 (0)