Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
7 changes: 6 additions & 1 deletion powershell/Maester.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,12 @@
'Test-MtCisaSafeLinkClickTracking', 'Test-MtCisaSafeLinkDownloadScan', 'Test-MtCisaSmtpAuthentication',
'Test-MtCisaSpamAction', 'Test-MtCisaSpamAlternative', 'Test-MtCisaSpamBypass', 'Test-MtCisaSpamFilter',
'Test-MtCisaSpfDirective', 'Test-MtCisaSpfRestriction', 'Test-MtCisaSpoSharing',
'Test-MtCisaSpoSharingAllowedDomain', 'Test-MtCisaUnmanagedRoleAssignment', 'Test-MtCisaWeakFactor',
'Test-MtCisaSpoSharingAllowedDomain',
'Test-MtCisaSpoOneDriveSharing', 'Test-MtCisaSpoDefaultSharingScope',
'Test-MtCisaSpoDefaultSharingPermission', 'Test-MtCisaSpoAnyoneLinkExpiration',
'Test-MtCisaSpoAnyoneLinkPermission', 'Test-MtCisaSpoVerificationCodeReauth',
'Get-MtSpo', 'Clear-MtSpoCache',
'Test-MtCisaUnmanagedRoleAssignment', 'Test-MtCisaWeakFactor',
'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtDeviceComplianceSettings',
'Test-MtExoRejectDirectSend',
'Test-MtExoSetScl',
Expand Down
1 change: 1 addition & 0 deletions powershell/Maester.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ $__MtSession = @{
DnsCache = @()
ExoCache = @{}
OrcaCache = @{}
SpoCache = @{}
}
New-Variable -Name __MtSession -Value $__MtSession -Scope Script -Force

Expand Down
1 change: 1 addition & 0 deletions powershell/internal/Clear-ModuleVariable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ function Clear-ModuleVariable {
$__MtSession.AdminPortalUrl = @{}
Clear-MtDnsCache
Clear-MtExoCache
Clear-MtSpoCache
# $__MtSession.Connections = @() # Do not clear connections as they are used to track the connection state. This module variable should only be set by Connect-Maester and Disconnect-Maester.
}
26 changes: 26 additions & 0 deletions powershell/public/Clear-MtSpoCache.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<#
.SYNOPSIS
Resets the local cache of SharePoint 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-MtSpoCache

This example clears the cache of all SPO requests.

.LINK
https://maester.dev/docs/commands/Clear-MtSpoCache
#>
function Clear-MtSpoCache {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification='Setting module level variable')]
[CmdletBinding()]
param()

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

$__MtSession.SpoCache = @{}
}
58 changes: 56 additions & 2 deletions powershell/public/Connect-Maester.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,18 @@
[string]$TeamsEnvironmentName = $null, #ToValidate: Don't use this parameter, this is the default.

# The services to connect to such as Azure and EXO. Default is Graph.
[ValidateSet('All', 'Azure', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'Teams')]
[ValidateSet('All', 'Azure', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'SharePointOnline', 'Teams')]
[string[]]$Service = 'Graph',

# The Tenant ID to connect to, if not specified the sign-in user's default tenant is used.
[string]$TenantId,

# The Client ID of the app to connect to for Graph. If not specified, the default Graph PowerShell CLI enterprise app will be used. Reference on how to create an enterprise app: https://learn.microsoft.com/en-us/powershell/microsoftgraph/authentication-commands?view=graph-powershell-1.0#use-delegated-access-with-a-custom-application-for-microsoft-graph-powershell
[string]$GraphClientId
[string]$GraphClientId,

# The SharePoint Online admin URL to connect to. If not specified, the URL will be derived from the tenant's initial domain.
# Example: https://contoso-admin.sharepoint.com
[string]$SharePointAdminUrl
)

$__MtSession.Connections = $Service
Expand Down Expand Up @@ -291,4 +295,54 @@
}
} # end switch OrderedImport

# SharePoint Online — not part of the Identity Client DLL ordering, handled separately
if ($Service -contains 'SharePointOnline' -or $Service -contains 'All') {
Write-Verbose 'Connecting to SharePoint Online'
try {
# Import the SPO module. On PowerShell 7+, use -UseWindowsPowerShell to proxy
# through a PS 5.1 session, avoiding the known 400 Bad Request compatibility issue.
if (-not (Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ErrorAction SilentlyContinue)) {
if ($PSVersionTable.PSEdition -eq 'Core') {
Write-Verbose "PowerShell Core detected — importing SPO module via Windows PowerShell compatibility layer"
Import-Module 'Microsoft.Online.SharePoint.PowerShell' -UseWindowsPowerShell -DisableNameChecking -ErrorAction Stop
} else {
Import-Module 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop
}
}

if ($SharePointAdminUrl) {
$spoAdminUrl = $SharePointAdminUrl
} else {
# Derive the SharePoint admin URL from the Graph context tenant domain
$graphContext = Get-MgContext
if ($null -eq $graphContext) {
Write-Host "`nPlease connect to Microsoft Graph first before connecting to SharePoint Online." -ForegroundColor Red
return
}

$domains = Invoke-MtGraphRequest -RelativeUri "domains" -ApiVersion "v1.0"
$tenantDomain = $domains |
Where-Object { $_.isInitial -eq $true } |
Select-Object -ExpandProperty id -First 1

if (-not $tenantDomain) {
Write-Host "`nUnable to determine tenant name for SharePoint Online admin URL. Use the -SharePointAdminUrl parameter to specify it manually." -ForegroundColor Red
Write-Host "Example: Connect-Maester -Service SharePointOnline -SharePointAdminUrl 'https://contoso-admin.sharepoint.com'" -ForegroundColor Yellow
return
}

$tenantName = $tenantDomain -replace '\.onmicrosoft\.com$', ''
$spoAdminUrl = "https://$tenantName-admin.sharepoint.com"
}

Write-Verbose "Connecting to SharePoint Online admin at $spoAdminUrl"
Connect-SPOService -Url $spoAdminUrl
} catch [Management.Automation.CommandNotFoundException] {
Write-Host "`nThe SharePoint Online PowerShell module is not installed. Please install the module using the following command." -ForegroundColor Red
Write-Host "`nInstall-Module Microsoft.Online.SharePoint.PowerShell -Scope CurrentUser`n" -ForegroundColor Yellow
} catch {
Write-Host "`nFailed to connect to SharePoint Online: $($_.Exception.Message)" -ForegroundColor Red
}
}

} # end function Connect-Maester
9 changes: 9 additions & 0 deletions powershell/public/Disconnect-Maester.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,13 @@ function Disconnect-Maester {
Write-Verbose -Message "Disconnecting from Microsoft Teams."
Disconnect-MicrosoftTeams
}

if($__MtSession.Connections -contains "SharePointOnline" -or $__MtSession.Connections -contains "All"){
Write-Verbose -Message "Disconnecting from SharePoint Online."
try {
Disconnect-SPOService -ErrorAction SilentlyContinue
} catch {
Write-Verbose "SPO disconnect: $($_.Exception.Message)"
}
}
}
47 changes: 47 additions & 0 deletions powershell/public/cisa/spo/Get-MtSpo.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<#
.SYNOPSIS
Retrieves cached SPO tenant settings or requests from Get-SPOTenant

.DESCRIPTION
Manages the SPO tenant settings caching. Calls Get-SPOTenant and caches the result
to avoid redundant calls during a test session.

Returns $null if the Microsoft.Online.SharePoint.PowerShell module is not loaded
or if Connect-SPOService has not been called.

.EXAMPLE
Get-MtSpo

Returns the cached SPO tenant settings object

.LINK
https://maester.dev/docs/commands/Get-MtSpo
#>
function Get-MtSpo {
[CmdletBinding()]
[OutputType([psobject])]
param()

if ($null -eq $__MtSession.SpoCache.SpoTenant) {
Write-Verbose "SPO tenant settings not in cache, requesting."

# Check if the SPO module is available
if (-not (Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ErrorAction SilentlyContinue)) {
Write-Verbose "Microsoft.Online.SharePoint.PowerShell module is not loaded."
return $null
}

# Check if connected to SPO by attempting to get tenant settings
try {
$response = Get-SPOTenant -ErrorAction Stop
$__MtSession.SpoCache.SpoTenant = $response
} catch {
Write-Verbose "Not connected to SharePoint Online or Get-SPOTenant failed: $($_.Exception.Message)"
return $null
}
} else {
Write-Verbose "SPO tenant settings in cache."
}

return $__MtSession.SpoCache.SpoTenant
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Expiration days for Anyone links SHALL be set to 30 days or less.

Rationale: Anyone links allow anonymous access to shared content. Setting an expiration of 30 days or less ensures that anonymous links do not persist indefinitely, reducing the window of exposure for unprotected content sharing.

This policy is only applicable if the external sharing slider on the admin page is set to Anyone.

#### Remediation action:

1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
2. Select **Policies** > **Sharing**.
3. Expand **More external sharing settings**.
4. Check **These links must expire within this many days** and set the value to **30** or less.
5. Select **Save**.

#### Related links

* [CISA 3 Anyone Links - MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1)
* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)

<!--- Results --->
%TestResult%
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<#
.SYNOPSIS
Checks if Anyone link expiration is set to 30 days or less

.DESCRIPTION
Expiration days for Anyone links SHALL be set to 30 days or less.

This test is only applicable when the tenant sharing capability is set to Anyone
(ExternalUserAndGuestSharing). If sharing is more restrictive, Anyone links are not
available and this control passes automatically.

.EXAMPLE
Test-MtCisaSpoAnyoneLinkExpiration

Returns true if Anyone link expiration is 30 days or less, or if Anyone sharing is disabled

.LINK
https://maester.dev/docs/commands/Test-MtCisaSpoAnyoneLinkExpiration
#>
function Test-MtCisaSpoAnyoneLinkExpiration {
[CmdletBinding()]
[OutputType([bool])]
param()

$spoTenant = Get-MtSpo

if ($null -eq $spoTenant) {
Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "SharePoint Online PowerShell module is not connected. Run Connect-SPOService first."
return $null
}

# This control only applies when sharing is set to Anyone
if ($spoTenant.SharingCapability -ne 'ExternalUserAndGuestSharing') {
$testResultMarkdown = "Well done. SharePoint sharing is not set to Anyone, so Anyone link expiration is not applicable. Current sharing level: **$($spoTenant.SharingCapability)**."
Add-MtTestResultDetail -Result $testResultMarkdown
return $true
}

$expirationDays = $spoTenant.RequireAnonymousLinksExpireInDays

$testResult = $expirationDays -ge 1 -and $expirationDays -le 30

if ($testResult) {
$testResultMarkdown = "Well done. Anyone link expiration is set to **$expirationDays** days.`n`n%TestResult%"
} else {
$testResultMarkdown = "Anyone link expiration is set to **$expirationDays** days. It should be between **1** and **30** days.`n`n%TestResult%"
}

$result = "| Setting | Value |`n"
$result += "| --- | --- |`n"
$result += "| RequireAnonymousLinksExpireInDays | $expirationDays |`n"
$result += "| SharingCapability | $($spoTenant.SharingCapability) |`n"

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

Add-MtTestResultDetail -Result $testResultMarkdown

return $testResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The allowable file and folder permissions for Anyone links SHALL be set to view only.

Rationale: Anyone links that grant edit permissions allow anonymous users to modify shared content. By restricting these links to view-only access, organizations prevent unauthorized modifications to files and folders accessed via anonymous links.

This policy is only applicable if the external sharing slider on the admin page is set to Anyone.

#### Remediation action:

1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
2. Select **Policies** > **Sharing**.
3. Expand **More external sharing settings**.
4. Under **Anyone links**, ensure **File permissions** is set to **View**.
5. Under **Anyone links**, ensure **Folder permissions** is set to **View**.
6. Select **Save**.

#### Related links

* [CISA 3 Anyone Links - MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1)
* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)

<!--- Results --->
%TestResult%
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<#
.SYNOPSIS
Checks if Anyone link permissions are set to view only

.DESCRIPTION
The allowable file and folder permissions for Anyone links SHALL be set to view only.

This test is only applicable when the tenant sharing capability is set to Anyone
(ExternalUserAndGuestSharing). If sharing is more restrictive, Anyone links are not
available and this control passes automatically.

Both FileAnonymousLinkType and FolderAnonymousLinkType must be set to View.

.EXAMPLE
Test-MtCisaSpoAnyoneLinkPermission

Returns true if Anyone link permissions are view only, or if Anyone sharing is disabled

.LINK
https://maester.dev/docs/commands/Test-MtCisaSpoAnyoneLinkPermission
#>
function Test-MtCisaSpoAnyoneLinkPermission {
[CmdletBinding()]
[OutputType([bool])]
param()

$spoTenant = Get-MtSpo

if ($null -eq $spoTenant) {
Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "SharePoint Online PowerShell module is not connected. Run Connect-SPOService first."
return $null
}

# This control only applies when sharing is set to Anyone
if ($spoTenant.SharingCapability -ne 'ExternalUserAndGuestSharing') {
$testResultMarkdown = "Well done. SharePoint sharing is not set to Anyone, so Anyone link permissions are not applicable. Current sharing level: **$($spoTenant.SharingCapability)**."
Add-MtTestResultDetail -Result $testResultMarkdown
return $true
}

$fileAnonymousLinkType = $spoTenant.FileAnonymousLinkType
$folderAnonymousLinkType = $spoTenant.FolderAnonymousLinkType

$testResult = $fileAnonymousLinkType -eq 'View' -and $folderAnonymousLinkType -eq 'View'

if ($testResult) {
$testResultMarkdown = "Well done. Anyone link permissions are set to **View** for both files and folders.`n`n%TestResult%"
} else {
$testResultMarkdown = "Anyone link permissions are not set to **View** for both files and folders. Both should be set to **View**.`n`n%TestResult%"
}

$result = "| Setting | Value | Result |`n"
$result += "| --- | --- | --- |`n"
$fileResult = if ($fileAnonymousLinkType -eq 'View') { "Pass" } else { "Fail" }
$folderResult = if ($folderAnonymousLinkType -eq 'View') { "Pass" } else { "Fail" }
$result += "| FileAnonymousLinkType | $fileAnonymousLinkType | $fileResult |`n"
$result += "| FolderAnonymousLinkType | $folderAnonymousLinkType | $folderResult |`n"

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

Add-MtTestResultDetail -Result $testResultMarkdown

return $testResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
File and folder default sharing permissions SHALL be set to view only.

Rationale: Setting the default link permissions to "View" limits sharing to view-only access for files and folders shared in SharePoint. This default reduces the risk of unintentional editing or modification of shared content by external parties or other users.

#### Remediation action:

1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
2. Select **Policies** > **Sharing**.
3. Under **File and folder links**, set the default permission to **View**.
4. Select **Save**.

#### Related links

* [CISA 2 Default Sharing Settings - MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1)
* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)

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