mirror of https://github.com/raandree/NTFSSecurity
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
16 KiB
295 lines
16 KiB
<#
|
|
Tests the error handling that the cmdlets with -Path share, with the module built in NTFSSecurity\bin\Release on
|
|
files in a sandbox folder: a path that doesn't exist, an item whose owner may not read its permissions, and an item
|
|
whose owner may not change its permissions, which the cmdlets that write the DACL handle by taking ownership. Each
|
|
error belongs to its path only, and the cmdlet continues with the next one. The tests turn the module setting
|
|
EnablePrivileges off, so that an elevated session meets the same denials as a basic user, and restore it.
|
|
#>
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
|
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
|
|
)]
|
|
param ()
|
|
|
|
BeforeDiscovery {
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
|
|
$holdsRestorePrivilege = Test-PrivilegeHeld -Name 'SeRestorePrivilege'
|
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
$readEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }
|
|
# An audit entry on a file has no inheritance flags.
|
|
$auditEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = 'None'; PropagationFlags = 'None' }
|
|
|
|
# Output: whether the cmdlet writes an object for the next path, an existing file.
|
|
$missingPathCases = @(
|
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Add-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Remove-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $true }; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false }
|
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
|
|
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
|
|
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
|
|
)
|
|
|
|
# The audit cmdlets need the Security privilege for the next path.
|
|
$missingPathAuditCases = @(
|
|
@{ Command = 'Get-NTFSAudit'; Parameters = @{} }
|
|
@{ Command = 'Add-NTFSAudit'; Parameters = $auditEntry }
|
|
@{ Command = 'Remove-NTFSAudit'; Parameters = $auditEntry }
|
|
@{ Command = 'Clear-NTFSAudit'; Parameters = @{} }
|
|
@{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{} }
|
|
@{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{} }
|
|
)
|
|
|
|
$deniedReadCases = @(
|
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; Output = $true }
|
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; Output = $true }
|
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; Output = $false }
|
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; Output = $true }
|
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; Output = $true }
|
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; Output = $true }
|
|
)
|
|
}
|
|
|
|
BeforeAll {
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
|
|
Import-Module -Name $modulePath -Force -ErrorAction Stop
|
|
$sandbox = New-TestSandbox -Name 'PathErrors'
|
|
Push-Location -LiteralPath $sandbox
|
|
|
|
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
|
|
$enablePrivileges = $privateData['EnablePrivileges']
|
|
$privateData['EnablePrivileges'] = $false
|
|
$sidType = [System.Security.Principal.SecurityIdentifier]
|
|
|
|
function Get-TestMissingPath {
|
|
$path = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
|
|
$path
|
|
}
|
|
|
|
function Get-TestAcl {
|
|
# .NET, because Get-Acl of an elevated Windows PowerShell reads also items that deny reading their permissions.
|
|
# .NET Core has the method as an extension method.
|
|
param ([string] $Path)
|
|
|
|
$info = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path
|
|
if ($PSVersionTable.PSEdition -eq 'Desktop') {
|
|
$info.GetAccessControl()
|
|
}
|
|
else {
|
|
[System.IO.FileSystemAclExtensions]::GetAccessControl($info)
|
|
}
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
Pop-Location
|
|
Remove-TestSandbox -Sandbox $sandbox
|
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Describe 'A path that does not exist' {
|
|
It '<Command> should write a <ErrorId> for it and continue with the next path' -ForEach $missingPathCases {
|
|
$missing = Get-TestMissingPath
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Next'
|
|
|
|
$output = @(& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
|
|
|
|
$pathErrors | Should -HaveCount 1
|
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
|
|
$pathErrors[0].TargetObject | Should -Be $missing
|
|
if ($Output) {
|
|
$output | Should -Not -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
It '<Command> should write a ReadFileError for it and continue with the next path' -ForEach $missingPathAuditCases -Skip:(-not $holdsSecurityPrivilege) {
|
|
$privateData['EnablePrivileges'] = $true
|
|
try {
|
|
$missing = Get-TestMissingPath
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextAudit'
|
|
|
|
& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue | Out-Null
|
|
}
|
|
finally {
|
|
$privateData['EnablePrivileges'] = $false
|
|
}
|
|
|
|
$pathErrors | Should -HaveCount 1
|
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
|
|
$pathErrors[0].TargetObject | Should -Be $missing
|
|
}
|
|
}
|
|
|
|
Describe 'An item whose owner may not read its permissions' {
|
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to read the security descriptor. Taking ownership
|
|
# can't help: the cmdlet must read the owner first, which needs the same right. Before 5.0.0-rc6,
|
|
# Get-NTFSOrphanedAccess reported this as an AddAceError.
|
|
It '<Command> should write a ReadSecurityError for it and continue with the next path' -ForEach $deniedReadCases {
|
|
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'Unreadable'
|
|
Block-TestReadPermission -Sandbox $sandbox -Path $blocked
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextReadable'
|
|
|
|
$output = @(& $Command -Path $blocked, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
|
|
|
|
$pathErrors | Should -HaveCount 1
|
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
$pathErrors[0].TargetObject | Should -Be $blocked
|
|
if ($Output) {
|
|
$output | Should -Not -BeNullOrEmpty
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'A denied write and a denied ownership retry' {
|
|
It '<Command> should keep the denied item unchanged, report <ErrorId>, and process the next item' -ForEach @(
|
|
@{ Command = 'Add-NTFSAccess'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }; ErrorId = 'AddAceError'; Operation = 'Add' }
|
|
@{ Command = 'Remove-NTFSAccess'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }; ErrorId = 'RemoveAceError'; Operation = 'Remove' }
|
|
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ClearAclError'; Operation = 'Clear' }
|
|
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ModifySdError'; Operation = 'Disable' }
|
|
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ModifySdError'; Operation = 'Enable' }
|
|
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $false }; ErrorId = 'ModifySdError'; Operation = 'Disable' }
|
|
) {
|
|
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryBlocked'
|
|
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryNext'
|
|
foreach ($path in $blocked, $next) {
|
|
if ($Operation -ne 'Add') {
|
|
Add-NTFSAccess -Path $path -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
|
|
}
|
|
if ($Operation -eq 'Enable') {
|
|
Disable-NTFSAccessInheritance -Path $path -ErrorAction Stop
|
|
}
|
|
}
|
|
Block-TestWritePermission -Sandbox $sandbox -Path $blocked
|
|
$before = (Get-TestAcl -Path $blocked).Sddl
|
|
|
|
& $Command -Path $blocked, $next @Parameters -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -HaveCount 1
|
|
$changeErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
|
|
$changeErrors[0].TargetObject | Should -Be $blocked
|
|
(Get-TestAcl -Path $blocked).Sddl | Should -BeExactly $before
|
|
$acl = Get-TestAcl -Path $next
|
|
$everyone = @($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
|
|
switch ($Operation) {
|
|
'Add' { $everyone | Should -HaveCount 1 }
|
|
'Remove' { $everyone | Should -BeNullOrEmpty }
|
|
'Clear' { @($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty }
|
|
'Disable' { $acl.AreAccessRulesProtected | Should -BeTrue }
|
|
'Enable' { $acl.AreAccessRulesProtected | Should -BeFalse }
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'An owner that the process cannot restore without the Restore privilege' {
|
|
It 'Should report RestoreOwnerError after a successful ownership retry and continue with the next path' -Skip:(-not $holdsRestorePrivilege) {
|
|
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'UnassignableOwner'
|
|
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'NextOwner'
|
|
$user = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $blocked -Rights @{ $user = 'ChangePermissions' }
|
|
$originalOwner = 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'
|
|
Set-TestOwner -Sandbox $sandbox -Path $blocked -Sid $originalOwner
|
|
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Restore').PrivilegeState | Should -Be 'Disabled'
|
|
|
|
Add-NTFSAccess -Path $blocked, $next -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -HaveCount 1
|
|
$changeErrors[0].FullyQualifiedErrorId | Should -BeLike 'RestoreOwnerError,*'
|
|
$changeErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
|
|
$changeErrors[0].TargetObject | Should -Be $blocked
|
|
(Get-TestAcl -Path $blocked).GetOwner($sidType).Value | Should -Be $user
|
|
foreach ($path in $blocked, $next) {
|
|
@((Get-TestAcl -Path $path).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
|
|
}
|
|
}
|
|
}
|
|
Describe 'An item whose owner may not change its permissions' {
|
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to change the DACL. The cmdlets take ownership,
|
|
# which Windows answers by removing the OWNER RIGHTS entries, write the DACL, and set the previous owner back.
|
|
BeforeEach {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchangeable'
|
|
$acl = Get-TestAcl -Path $file
|
|
$owner = $acl.GetOwner($sidType).Value
|
|
}
|
|
|
|
It 'Add-NTFSAccess should take ownership, add the entry, and set the owner back' {
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
|
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-3-4' }) | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Remove-NTFSAccess should take ownership, remove the entry, and set the owner back' {
|
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Remove-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Clear-NTFSAccess should take ownership, remove the explicit entries, and set the owner back' {
|
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Clear-NTFSAccess -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' {
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Disable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
$acl.AreAccessRulesProtected | Should -BeTrue
|
|
}
|
|
|
|
It 'Enable-NTFSAccessInheritance should take ownership, let the DACL inherit, and set the owner back' {
|
|
Disable-NTFSAccessInheritance -Path $file
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Enable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
$acl.AreAccessRulesProtected | Should -BeFalse
|
|
}
|
|
|
|
It 'Set-NTFSInheritance should take ownership, protect the DACL, and set the owner back' {
|
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
|
|
|
|
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable changeErrors -ErrorAction SilentlyContinue
|
|
|
|
$changeErrors | Should -BeNullOrEmpty
|
|
$acl = Get-TestAcl -Path $file
|
|
$acl.GetOwner($sidType).Value | Should -Be $owner
|
|
$acl.AreAccessRulesProtected | Should -BeTrue
|
|
}
|
|
}
|
|
|