Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/powershell/tests/Test-Assessment.41080.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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.

## Remediation resources

- [Protect apps with Microsoft Defender for Cloud Apps Conditional Access App Control](https://learn.microsoft.com/en-us/defender-cloud-apps/proxy-intro-aad)
- [Deploy Conditional Access App Control for featured apps](https://learn.microsoft.com/en-us/defender-cloud-apps/proxy-deployment-aad)
- [Create access and session policies](https://learn.microsoft.com/en-us/defender-cloud-apps/session-policy-aad)
- [Conditional Access policy resource type](https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy)

<!--- Results --->
%TestResult%
178 changes: 178 additions & 0 deletions src/powershell/tests/Test-Assessment.41080.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<#
.SYNOPSIS
Checks that Conditional Access App Control session policies are enabled for cloud apps.

.NOTES
Test ID: 41080
Workshop Task: SECOPS-080
Pillar: SecOps
Category: Identity threat protection
Required permission: Policy.Read.All
#>

function Test-Assessment-41080 {

[ZtTest(
Category = 'Identity threat protection',
CompatibleLicense = ('ADALLOM_S_STANDALONE&AAD_PREMIUM'),
Comment thread
Manoj-Kesana marked this conversation as resolved.
ImplementationCost = 'Medium',
Pillar = 'SecOps',
RiskLevel = 'High',
Service = ('Graph'),
SfiPillar = 'Protect identities and secrets',
TenantType = ('Workforce'),
TestId = 41080,
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps',
UserImpact = 'Medium'
)]
[CmdletBinding()]
param()

#region Data Collection
Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
$activity = 'Checking Conditional Access App Control session policies'
Write-ZtProgress -Activity $activity -Status 'Querying enabled Conditional Access policies'

try {
$enabledPolicies = Invoke-ZtGraphRequest -RelativeUri 'identity/conditionalAccess/policies' -ApiVersion beta -Filter "state eq 'enabled'" -Select 'id,displayName,state,conditions,sessionControls' -ErrorAction Stop
}
catch {
$httpStatus = Get-ZtHttpStatusCode -ErrorRecord $_
Write-PSFMessage "Failed to retrieve Conditional Access policies (HTTP $httpStatus): $_" -Tag Test -Level Warning

$resultMessage = if ($httpStatus -in @(401, 403)) {
'⚠️ 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.'
}
else {
'⚠️ 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.'
}

$params = @{
TestId = '41080'
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps'
Status = $false
Result = $resultMessage
CustomStatus = 'Investigate'
}
Add-ZtTestResultDetail @params
return
}
#endregion Data Collection

#region Assessment Logic
$enabledPolicies = @($enabledPolicies)

$policyResults = foreach ($policy in $enabledPolicies) {
$targetApps = @($policy.conditions.applications.includeApplications | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
$sessionControl = $policy.sessionControls.cloudAppSecurity
$isEnabled = $null -ne $sessionControl -and $sessionControl.isEnabled -eq $true
$isMatchingPolicy = $isEnabled -and $targetApps.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($sessionControl.cloudAppSecurityType)
Comment thread
Manoj-Kesana marked this conversation as resolved.
Outdated

$knownTargets = @($targetApps | Where-Object { $_ -in @('All', 'Office365', 'MicrosoftAdminPortals') } | ForEach-Object {
switch ($_) {
'All' { 'All cloud apps' }
'Office365' { 'Office 365' }
'MicrosoftAdminPortals' { 'Microsoft admin portals' }
}
})
$selectedTargetCount = @($targetApps | Where-Object { $_ -notin @('All', 'Office365', 'MicrosoftAdminPortals') }).Count
if ($selectedTargetCount -gt 0) {
$selectedTargetLabel = if ($selectedTargetCount -eq 1) { '1 selected app' } else { "$selectedTargetCount selected apps" }
$knownTargets += $selectedTargetLabel
}

$cloudAppSecurityType = if ($sessionControl.cloudAppSecurityType) { $sessionControl.cloudAppSecurityType } else { 'Not configured' }
$rowStatus = if (-not $isMatchingPolicy) {
'Fail'
}
elseif ($cloudAppSecurityType -eq 'unknownFutureValue') {
'Investigate'
}
else {
'Pass'
}

[PSCustomObject]@{
PolicyDisplayName = $policy.displayName
PolicyId = $policy.id
State = $policy.state
TargetApps = if ($knownTargets.Count -gt 0) { $knownTargets -join ', ' } else { 'None' }
CloudAppSecurityType = $cloudAppSecurityType
IsEnabled = $isEnabled
Matches = $isMatchingPolicy
RowStatus = $rowStatus
}
}

$policyResults = @($policyResults)
$matchingPolicies = @($policyResults | Where-Object Matches)
$knownMatchingPolicies = @($matchingPolicies | Where-Object CloudAppSecurityType -ne 'unknownFutureValue')

$passed = $false
$customStatus = $null
if ($knownMatchingPolicies.Count -gt 0) {
$passed = $true
$testResultMarkdown = "✅ At least one enabled Conditional Access policy enforces Microsoft Defender for Cloud Apps session control via Conditional Access App Control.`n`n%TestResult%"
}
elseif ($matchingPolicies.Count -gt 0) {
$customStatus = 'Investigate'
$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%"
}
else {
$testResultMarkdown = "❌ No enabled Conditional Access policy has Defender for Cloud Apps session control enabled for a cloud app.`n`n%TestResult%"
}
#endregion Assessment Logic

#region Report Generation
$portalUrl = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade'
$policyUrlTemplate = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/{0}'
$maxDisplay = 10
$displayPolicies = @($policyResults | Sort-Object -Property @{ Expression = { -not $_.Matches } }, PolicyDisplayName | Select-Object -First $maxDisplay)
Comment thread
Manoj-Kesana marked this conversation as resolved.
Outdated

$tableRows = ''
foreach ($policy in $displayPolicies) {
$policyName = "[$(Get-SafeMarkdown -Text $policy.PolicyDisplayName)]($($policyUrlTemplate -f $policy.PolicyId))"
$targetApps = Get-SafeMarkdown -Text $policy.TargetApps
$securityType = Get-SafeMarkdown -Text $policy.CloudAppSecurityType
$isEnabled = if ($policy.IsEnabled) { 'True' } else { 'False' }
$status = switch ($policy.RowStatus) {
'Pass' { '✅ Pass' }
'Fail' { '❌ Fail' }
'Investigate' { '⚠️ Investigate' }
}
$tableRows += "| $policyName | $($policy.State) | $targetApps | $securityType | $isEnabled | $status |`n"
}

if ($policyResults.Count -eq 0) {
$tableRows = "| No enabled policies found | — | — | — | False | ❌ Fail |`n"
}
elseif ($policyResults.Count -gt $maxDisplay) {
$remaining = $policyResults.Count - $maxDisplay
$tableRows += "`n... and $remaining more. [Microsoft Entra > Conditional Access > Policies]($portalUrl)`n"
}

$formatTemplate = @'


## [Microsoft Entra > Conditional Access > Policies]({0})

| Policy display name | State | Target apps | Cloud App Security type | Is enabled | Status |
| :------------------ | :---- | :---------- | :---------------------- | :--------- | :----- |
{1}
'@

$mdInfo = $formatTemplate -f $portalUrl, $tableRows
$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
#endregion Report Generation

$params = @{
TestId = '41080'
Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps'
Status = $passed
Result = $testResultMarkdown
}
if ($customStatus) {
$params.CustomStatus = $customStatus
}
Add-ZtTestResultDetail @params
}