Skip to content

Commit

Permalink
Merge pull request #415 from Snozzberries/cisaExo
Browse files Browse the repository at this point in the history
Added CISA EXO section 9
  • Loading branch information
merill authored Aug 13, 2024
2 parents 635e454 + 73e139b commit 9a53255
Show file tree
Hide file tree
Showing 57 changed files with 1,323 additions and 27 deletions.
6 changes: 5 additions & 1 deletion powershell/Maester.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae
'Test-MtCisaDmarcRecordExist', 'Test-MtCisaDmarcRecordReject',
'Test-MtCisaDmarcAggregateCisa', 'Test-MtCisaDmarcReport',
'Test-MtCisaDlp', 'Test-MtCisaDlpPii', 'Test-MtCisaDlpAlternate',
'Test-MtCisaDlpBaselineRule',
'Test-MtCisaDlpBaselineRule', 'Test-MtCisaAttachmentFilter',
'Test-MtCisaAttachmentFileType', 'Test-MtCisaEmailFilterAlternative',
'Test-MtCisaBlockExecutable', 'Test-MtCisaMalwareAction', 'Test-MtCisaMalwareZap',
'Test-MtCisaImpersonation', 'Test-MtCisaImpersonationTip', 'Test-MtCisaMailboxIntelligence',
'Get-MtExo', 'Clear-MtExoCache',
'Test-MtConditionalAccessWhatIf',
'Test-MtConnection',
'Test-MtEidscaControl',
Expand Down
1 change: 1 addition & 0 deletions powershell/Maester.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ $__MtSession = @{
TestResultDetail = @{}
Connections = @()
DnsCache = @()
ExoCache = @{}
}
New-Variable -Name __MtSession -Value $__MtSession -Scope Script -Force

Expand Down
1 change: 1 addition & 0 deletions powershell/internal/Get-MtSkippedReason.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function Get-MtSkippedReason {
"NotLicensedEntraIDGovernance" { "This test is for tenants that are licensed for Entra ID Governance. See [Entra ID Governance licensing](https://learn.microsoft.com/entra/fundamentals/licensing#microsoft-entra-id-governance)"; break}
"NotLicensedEntraWorkloadID" { "This test is for tenants that are licensed for Entra Workload ID. See [Entra Workload ID licensing](https://learn.microsoft.com/entra/workload-id/workload-identities-faqs)"; break}
"NotLicensedExoDlp" { "This test is for tenants that are licensed for Exchange Online DLP. See [Microsoft Purview Data Loss Prevention: Data Loss Prevention (DLP) for Exchange Online, SharePoint Online, and OneDrive for Business](https://learn.microsoft.com/en-us/office365/servicedescriptions/microsoft-365-service-descriptions/microsoft-365-tenantlevel-services-licensing-guidance/microsoft-365-security-compliance-licensing-guidance#which-licenses-provide-the-rights-for-a-user-to-benefit-from-the-service-7)"; break}
"NotLicensedMdo" { "This test is for tenants that are licensed for Defender for Office 365 Plan 2. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break}
"LicensedEntraIDPremium" { "This test is for tenants that are not licensed for any Entra ID Premium license. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
"NotSupported" { "This test relies on capabilities not currently available (e.g., cmdlets that are not available on all platforms, Resolve-DnsName)"; break}
default { $SkippedBecause; break}
Expand Down
3 changes: 2 additions & 1 deletion powershell/public/Add-MtTestResultDetail.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ function Add-MtTestResultDetail {

[Parameter(Mandatory = $false)]
[ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance',
'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', 'NotLicensedExoDlp', "LicensedEntraIDPremium", 'NotSupported', 'Custom'
'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', 'NotLicensedExoDlp', "LicensedEntraIDPremium", 'NotSupported', 'Custom',
'NotLicensedMdo'
)]
# Common reasons for why the test was skipped.
[string] $SkippedBecause,
Expand Down
26 changes: 26 additions & 0 deletions powershell/public/Clear-MtExoCache.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<#
.SYNOPSIS
Resets the local cache of Exchange Online queries. Use this if you need to force a refresh of the cache in the current session.
.DESCRIPTION
By default all requests are cached and re-used for the duration of the session.
Use this function to clear the cache and force a refresh of the data.
.EXAMPLE
Clear-MtExoCache
This example clears the cache of all EXO requests.
.LINK
https://maester.dev/docs/commands/Clear-MtExoCache
#>
function Clear-MtExoCache {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification='Setting module level variable')]
[CmdletBinding()]
param()

Write-Verbose -Message "Clearing the results for EXO requests in this session"

$__MtSession.ExoCache = @{}
}
23 changes: 21 additions & 2 deletions powershell/public/Get-MtLicenseInformation.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function Get-MtLicenseInformation {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, Mandatory)]
[ValidateSet('EntraID', 'EntraWorkloadID', 'ExoDlp')]
[ValidateSet('EntraID', 'EntraWorkloadID', 'ExoDlp', 'Mdo')]
[string] $Product
)

Expand Down Expand Up @@ -73,7 +73,26 @@ function Get-MtLicenseInformation {
$LicenseType = "ExoDlp"
}
}
Write-Information "The license type for Entra ID is $LicenseType"
Write-Information "The license type for Exchange Online DLP is $LicenseType"
return $LicenseType
Break
}
"Mdo" {
Write-Verbose "Retrieving license SKU for ExoDlp"
$skus = Invoke-MtGraphRequest -RelativeUri "subscribedSkus"
$requiredSkus = @(
#servicePlanId
"8e0c0a52-6a6c-4d40-8370-dd62790dcd70" #Microsoft Defender for Office 365 (Plan 2)
)
$LicenseType = $null
foreach($sku in $requiredSkus){
$skuId = $sku -in $skus.skuId
$servicePlanId = $sku -in $skus.servicePlans.servicePlanId
if($skuId -or $servicePlanId){
$LicenseType = "Mdo"
}
}
Write-Information "The license type for Defender for Office is $LicenseType"
return $LicenseType
Break
}
Expand Down
104 changes: 104 additions & 0 deletions powershell/public/cisa/exchange/Get-MtExo.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<#
.SYNOPSIS
Retrieves cached response or requests from cmdlet
.DESCRIPTION
Manages the EXO cmdlet caching
.PARAMETER Request
Provide the name of the EXO Cmdlet without the Get- prepended (e.g. Get-AcceptedDomain = -Request AcceptedDomain)
.EXAMPLE
Get-MtExo -Request AcceptedDomain
Returns accepted domains for a tenant
.EXAMPLE
Get-MtAcceptedDomain
Returns accepted domains for a tenant
.LINK
https://maester.dev/docs/commands/Get-MtExo
#>
function Get-MtExo {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression","")]
[Alias(
"Get-MtAcceptedDomain",
"Get-MtRemoteDomain",
"Get-MtTransportConfig",
"Get-MtTransportRule",
"Get-MtOrganizationConfig",
"Get-MtDkimSigningConfig",
"Get-MtSharingPolicy",
"Get-MtDlpComplianceRule",
"Get-MtDlpCompliancePolicy",
"Get-MtMalwareFilterPolicy",
"Get-MtHostedContentFilterPolicy",
"Get-MtAntiPhishPolicy",
"Get-MtSafeAttachmentPolicy",
"Get-MtSafeLinksPolicy",
"Get-MtATPBuiltInProtectionRule",
"Get-MtEOPProtectionPolicyRule",
"Get-MtATPProtectionPolicyRule"
)]
[CmdletBinding()]
[OutputType([string],[object[]],[psobject])]
param(
[string] $Request = ($MyInvocation.InvocationName).Substring(6)
)
<#
$policies = @{
"HostedContentFilterPolicy" = Get-HostedContentFilterPolicy #RecommendedPolicyType -eq "Standard", "Strict"
"SafeAttachmentPolicy" = Get-SafeAttachmentPolicy #RecommendedPolicyType -eq "Standard", "Strict"
"SafeLinksPolicy" = Get-SafeLinksPolicy #RecommendedPolicyType -eq "Standard", "Strict"
"ATPBuiltInProtectionRule" = Get-ATPBuiltInProtectionRule
"EOPProtectionPolicyRule" = Get-EOPProtectionPolicyRule #-Identity "*Preset Security Policy" #IsBuiltInProtection
"ATPProtectionPolicyRule" = Get-ATPProtectionPolicyRule #-Identity "*Preset Security Policy" #IsBuiltInProtection
}
#>

### To add new commands
### - add them to the hashtable below
### - add them as an alias
### - confirm the command's return type is in OutputType (e.g. (Get-AcceptedDomain).GetType().Name)
$commands = @{
"AcceptedDomain" = "Get-AcceptedDomain"
"RemoteDomain" = "Get-RemoteDomain"
"TransportConfig" = "Get-TransportConfig"
"TransportRule" = "Get-TransportRule"
"OrganizationConfig" = "Get-OrganizationConfig"
"DkimSigningConfig" = "Get-DkimSigningConfig"
"SharingPolicy" = "Get-SharingPolicy"
"DlpComplianceRule" = "Get-DlpComplianceRule"
"DlpCompliancePolicy" = "Get-DlpCompliancePolicy"
"MalwareFilterPolicy" = "Get-MalwareFilterPolicy"
"HostedContentFilterPolicy" = "Get-HostedContentFilterPolicy"
"AntiPhishPolicy" = "Get-AntiPhishPolicy"
"SafeAttachmentPolicy" = "Get-SafeAttachmentPolicy"
"SafeLinksPolicy" = "Get-SafeLinksPolicy"
"ATPBuiltInProtectionRule" = "Get-ATPBuiltInProtectionRule"
"EOPProtectionPolicyRule" = "Get-EOPProtectionPolicyRule"
"ATPProtectionPolicyRule" = "Get-ATPProtectionPolicyRule"
}


if($Request -eq "Exo"){
Write-Error "$($MyInvocation.InvocationName) called with invalid -Request, specify value (e.g., AcceptedDomain)"
return "Unable to obtain policy"
}elseif($Request -notin $commands.Keys){
Write-Error "$($MyInvocation.InvocationName) called with unsupported -Request"
return "Unable to obtain policy"
}

if($null -eq $__MtSession.ExoCache.$Request){
Write-Verbose "$request not in cache, requesting."
$response = Invoke-Expression $commands.$Request
$__MtSession.ExoCache.$Request = $response
}else{
Write-Verbose "$request in cache."
$response = $__MtSession.ExoCache.$Request
}

return $response
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Test-MtCisaAntiSpamAllowList {
return $null
}

$policy = Get-HostedConnectionFilterPolicy
$policy = Get-MtHostedConnectionFilterPolicy

$resultPolicy = $policy | Where-Object {`
($_.IPAllowList | Measure-Object).Count -gt 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Test-MtCisaAntiSpamSafeList {
return $null
}

$policy = Get-HostedConnectionFilterPolicy
$policy = Get-MtHostedConnectionFilterPolicy

$resultPolicy = $policy | Where-Object {`
-not $_.EnableSafeList
Expand Down
24 changes: 24 additions & 0 deletions powershell/public/cisa/exchange/Test-MtCisaAttachmentFileType.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
The attachment filter SHOULD attempt to determine the true file type and assess the file extension.

Rationale: Users can change a file extension at the end of a file name (e.g., notepad.exe to notepad.txt) to obscure the actual file type. Verifying the file type and checking that this matches the designated file extension can help detect instances where the file extension was changed.

#### Remediation action:

1. Sign in to **Microsoft 365 Defender**.
2. In the left-hand menu, go to **Email & Collaboration** > **Policies & Rules**.
3. Select **Threat Policies**.
4. From the **Templated policies** section, select [**Preset Security Policies**](https://security.microsoft.com/presetSecurityPolicies).
5. Under **Standard protection**, slide the toggle switch to the right so the text next to the toggle reads **Standard protection is on**.
6. Under **Strict protection**, slide the toggle switch to the right so the text next to the toggle reads **Strict protection is on**.

Note: If the toggle slider in step 5 is grayed out, click on **Manage protection settings** instead and configure the policy settings according to [Use the Microsoft 365 Defender portal to assign Standard and Strict preset security policies to users | Microsoft Learn](https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/preset-security-policies?view=o365-worldwide#use-the-microsoft-365-defender-portal-to-assign-standard-and-strict-preset-security-policies-to-users).

#### Related links

* [Defender admin center - Preset security policies](https://security.microsoft.com/presetSecurityPolicies)
* [CISA 9 Attachment File Type - MS.EXO.9.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo92v1)
* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/EXOConfig.rego#L502)
* [Microsoft Learn - True type matching in the common attachments filter](https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about#true-type-matching-in-the-common-attachments-filter)

<!--- Results --->
%TestResult%
86 changes: 86 additions & 0 deletions powershell/public/cisa/exchange/Test-MtCisaAttachmentFileType.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<#
.SYNOPSIS
Checks state of preset security policies
.DESCRIPTION
Emails SHALL be filtered by attachment file types
.EXAMPLE
Test-MtCisaAttachmentFileType
Returns true if standard and strict protection is on
.LINK
https://maester.dev/docs/commands/Test-MtCisaAttachmentFileType
#>
function Test-MtCisaAttachmentFileType {
[CmdletBinding()]
[OutputType([bool])]
param()

if (!(Test-MtConnection ExchangeOnline)) {
Add-MtTestResultDetail -SkippedBecause NotConnectedExchange
return $null
} elseif (!(Test-MtConnection SecurityCompliance)) {
Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance
return $null
} elseif ($null -eq (Get-MtLicenseInformation -Product Mdo)) {
Add-MtTestResultDetail -SkippedBecause NotLicensedMdo
return $null
}

$policies = Get-MtMalwareFilterPolicy

$fileFilter = $policies | Where-Object { `
$_.EnableFileFilter
}

$standard = $policies | Where-Object { `
$_.RecommendedPolicyType -eq "Standard"
}

$strict = $policies | Where-Object { `
$_.RecommendedPolicyType -eq "Strict"
}

$testResult = $standard -and $strict -and (($fileFilter|Measure-Object).Count -ge 1)

$portalLink = "https://security.microsoft.com/presetSecurityPolicies"
$passResult = "✅ Pass"
$failResult = "❌ Fail"

if ($testResult) {
$testResultMarkdown = "Well done. Your tenant has [standard and strict preset security policies for the common file filter]($portalLink).`n`n%TestResult%"
} else {
$testResultMarkdown = "Your tenant does not have [standard and strict preset security policies enabled]($portalLink).`n`n%TestResult%"
}

$result = "| Policy | Status |`n"
$result += "| --- | --- |`n"
if ($standard) {
$result += "| Standard | $passResult |`n"
} else {
$result += "| Standard | $failResult |`n"
}
if ($strict) {
$result += "| Strict | $passResult |`n"
} else {
$result += "| Strict | $failResult |`n`n"
}

$result += "| Policy Name | File Filter Enabled |`n"
$result += "| --- | --- |`n"
foreach($item in $policies | Sort-Object -Property Identity){
if($item.EnableFileFilter){
$result += "| $($item.Identity) | $($passResult) |`n"
}else{
$result += "| $($item.Identity) | $($failResult) |`n"
}
}

$testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result

Add-MtTestResultDetail -Result $testResultMarkdown

return $testResult
}
24 changes: 24 additions & 0 deletions powershell/public/cisa/exchange/Test-MtCisaAttachmentFilter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Emails SHALL be filtered by attachment file types.

Rationale: Malicious attachments often take the form of click-to-run files. Sharing high risk file types, when necessary, is better left to a means other than email; the dangers of allowing them to be sent over email outweigh any potential benefits. Filtering email attachments based on file types can prevent spread of malware distributed via click-to-run email attachments.

#### Remediation action:

1. Sign in to **Microsoft 365 Defender**.
2. In the left-hand menu, go to **Email & Collaboration** > **Policies & Rules**.
3. Select **Threat Policies**.
4. From the **Templated policies** section, select **Preset Security Policies**.
5. Under **Standard protection**, slide the toggle switch to the right so the text next to the toggle reads **Standard protection is on**.
6. Under **Strict protection**, slide the toggle switch to the right so the text next to the toggle reads **Strict protection is on**.

Note: If the toggle slider in step 5 is grayed out, click on **Manage protection settings** instead and configure the policy settings according to [Use the Microsoft 365 Defender portal to assign Standard and Strict preset security policies to users | Microsoft Learn](https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/preset-security-policies?view=o365-worldwide#use-the-microsoft-365-defender-portal-to-assign-standard-and-strict-preset-security-policies-to-users).

#### Related links

* [Defender admin center - Preset security policies](https://security.microsoft.com/presetSecurityPolicies)
* [CISA 9 Attachment File Type - MS.EXO.9.1v2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo91v2)
* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/EXOConfig.rego#L487)
* [Microsoft Learn - Common attachments filter in anti-malware policies](https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about#common-attachments-filter-in-anti-malware-policies)

<!--- Results --->
%TestResult%
Loading

0 comments on commit 9a53255

Please sign in to comment.