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.
 
 

236 lines
12 KiB

<#
Tests Get-NTFSSecurityDescriptor and Set-NTFSSecurityDescriptor of the module built in NTFSSecurity\bin\Release on
files in a sandbox folder. Tests that need the Security or the Restore privilege skip without it; CI runs them
elevated.
#>
[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'
# Assigning an owner other than the user or one of its groups needs the Restore privilege.
$canAssignAnyOwner = Test-PrivilegeHeld -Name 'SeRestorePrivilege'
}
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 'SecurityDescriptor'
Push-Location -LiteralPath $sandbox
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
$enablePrivileges = $privateData['EnablePrivileges']
$sidType = [System.Security.Principal.SecurityIdentifier]
# An owner that the user can assign only with the Restore privilege
$trustedInstaller = 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'
function Get-RestorePrivilegeState {
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Restore').PrivilegeState
}
function Get-EveryoneRule ([string] $Path) {
(Get-Acl -LiteralPath $Path).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }
}
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-NTFSSecurityDescriptor' {
# Before 5.0.0-rc3, the cmdlet read the DACL together with the SACL when the process held the Security privilege.
# When the folder has no SACL, Windows then returns the inherited entries of a DACL without the auto-inherit flag,
# such as that of a file in the temp folder of the user, without their inherited flag.
It 'Should report the inherited access entries as inherited' -Skip:(-not $holdsSecurityPrivilege) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Inherited'
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count
$inheritedCount | Should -BeGreaterThan 0
$sd = Get-NTFSSecurityDescriptor -Path $file
@($sd.SecurityDescriptor.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
@($sd.SecurityDescriptor.GetAccessRules($false, $true, $sidType)) | Should -HaveCount $inheritedCount
}
It 'Should read the owner and the audit entries with the access entries' -Skip:(-not $holdsSecurityPrivilege) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Sections'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
$sd = Get-NTFSSecurityDescriptor -Path $file
$sd.SecurityDescriptor.GetOwner($sidType).Value | Should -Be (Get-Acl -LiteralPath $file).GetOwner($sidType).Value
@($sd.SecurityDescriptor.GetAuditRules($true, $false, $sidType)) | Should -HaveCount 1
}
}
Describe 'Set-NTFSSecurityDescriptor' {
Context 'When a cmdlet added an access entry to the descriptor' {
It 'Should write the entry as the only explicit entry of the item' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddedEntry'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd
@(Get-EveryoneRule -Path $file) | Should -HaveCount 1
@((Get-Acl -LiteralPath $file).GetAccessRules($true, $false, $sidType)) | Should -HaveCount 1
}
}
Context 'When the item has an owner that the user cannot assign' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
# Before 5.0.0-rc3, the cmdlet wrote every section that Get-NTFSSecurityDescriptor read, also the unchanged
# owner, which Windows refuses without the Restore privilege, like a file server that refuses the owner (#34).
It 'Should write an added access entry and keep the owner' -Skip:(-not $canAssignAnyOwner) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OtherOwner'
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
Get-RestorePrivilegeState | Should -Be 'Disabled'
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -BeNullOrEmpty
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $trustedInstaller
@(Get-EveryoneRule -Path $file) | Should -HaveCount 1
}
It 'Should write an added audit entry and keep the owner' -Skip:(-not ($holdsSecurityPrivilege -and $canAssignAnyOwner)) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OtherOwnerAudit'
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
Get-RestorePrivilegeState | Should -Be 'Disabled'
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -BeNullOrEmpty
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $trustedInstaller
@(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -HaveCount 1
}
}
Context 'When the descriptor has sections that it did not change' {
# Before 5.0.0-rc3, the cmdlet wrote every section that Get-NTFSSecurityDescriptor read, so it also undid the
# changes that were made to the item after it was read.
It 'Should not write back the access entries of an unchanged descriptor' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchanged'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file
$acl = Get-Acl -LiteralPath $file
$acl.AddAccessRule((New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
(New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-1-0'),
[System.Security.AccessControl.FileSystemRights]::ReadData, [System.Security.AccessControl.AccessControlType]::Allow
)))
Set-Acl -LiteralPath $file -AclObject $acl
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd
@(Get-EveryoneRule -Path $file) | Should -HaveCount 1
}
It 'Should write the owner when only the owner changed' -Skip:(-not $canAssignAnyOwner) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NewOwner'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file
$sd.SecurityDescriptor.SetOwner((New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList $trustedInstaller))
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $trustedInstaller
}
}
Context 'With -Verbose' {
It 'Should name the sections that it writes' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'VerboseChanged'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
$messages = Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -Verbose 4>&1
$messages.Message | Should -Contain "Writing the changed sections of the security descriptor of '$($sd.FullName)': Access"
}
It 'Should say that it writes nothing for an unchanged descriptor' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'VerboseUnchanged'
$sd = Get-NTFSSecurityDescriptor -Path $file
$messages = Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -Verbose 4>&1
$messages.Message |
Should -Contain "No section of the security descriptor of '$($sd.FullName)' changed since it was read or last written; nothing is written"
}
}
Context 'When the write is denied until the cmdlet takes ownership' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
# Before 5.0.0-rc4, the cmdlet set the previous owner back after the write, which undid an owner that the
# descriptor set, and failed for a previous owner that the user can't assign. The deny entry for the user stops
# the first write; as the owner, the user may change the permissions.
It 'Should keep the owner that the descriptor sets when the write succeeds' -Skip:(-not $canAssignAnyOwner) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RetryOwner'
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ $currentUser = 'ChangePermissions' }
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
Get-RestorePrivilegeState | Should -Be 'Disabled'
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAccess -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData
$sd.SecurityDescriptor.SetOwner((New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-5-32-544'))
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorVariable setErrors -ErrorAction SilentlyContinue
$setErrors | Should -BeNullOrEmpty
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be 'S-1-5-32-544'
}
}
}
Describe 'FileSystemSecurity2.Write with another item' {
# Before 5.0.0-rc4, Write wrote every section that the descriptor held to the other item, also the owner that
# Windows returns with a DACL without the auto-inherit flag, which fails for an owner that the user can't assign.
It 'Should write only the sections that were read, given the item as <_>' -Skip:(-not $canAssignAnyOwner) -ForEach @(
'FileSystemInfo', 'String'
) {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'Source'
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Target'
Set-TestOwner -Sandbox $sandbox -Path $source -Sid $trustedInstaller
$targetOwner = (Get-Acl -LiteralPath $target).GetOwner($sidType).Value
Get-RestorePrivilegeState | Should -Be 'Disabled'
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
(Get-Item2 -Path $source), [System.Security.AccessControl.AccessControlSections]::Access
)
$sd.SecurityDescriptor.GetOwner($sidType).Value | Should -Be $trustedInstaller
Assert-TestSandboxPath -Sandbox $sandbox -Path $target
$destination = if ($_ -eq 'String') { $target } else { Get-Item2 -Path $target }
{ $sd.Write($destination) } | Should -Not -Throw
(Get-Acl -LiteralPath $target).GetOwner($sidType).Value | Should -Be $targetOwner
}
}