Browse Source

fix(audit): write a ReadSecurityError in Get-NTFSOrphanedAudit

Without the Security privilege, Get-NTFSOrphanedAudit read the item
without its SACL and returned nothing, which looked like an item without
orphaned entries, and it wrote a warning for an item that it couldn't
read. It now reads only the SACL, like Get-NTFSAudit, and writes a
ReadSecurityError with the category PermissionDenied or OpenError. The
error for a path that doesn't exist stays ReadError.

Decision 22, item 1: an assumption in autopilot, flagged for the
maintainer's review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/116/head
Raimund Andree 3 days ago
parent
commit
3899228508
  1. 4
      CHANGELOG.md
  2. 8
      Docs/Cmdlets/Get-NTFSOrphanedAudit.md
  3. 12
      NTFSSecurity/AuditCmdlets/Get-OrphanedAudit.cs
  4. 6
      NTFSSecurity/AuditCmdlets/GetAudit.cs
  5. 7
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  6. 24
      Tests/Audit.Tests.ps1

4
CHANGELOG.md

@ -328,5 +328,9 @@ The format is based on
entry, and `Get-NTFSAccess -SecurityDescriptor` stopped with an entry, and `Get-NTFSAccess -SecurityDescriptor` stopped with an
`ArgumentOutOfRangeException` for a descriptor with audit entries, such `ArgumentOutOfRangeException` for a descriptor with audit entries, such
as one that `Get-NTFSSecurityDescriptor` reads in an elevated session as one that `Get-NTFSSecurityDescriptor` reads in an elevated session
- Fix `Get-NTFSOrphanedAudit`, which returned nothing without the Security
privilege, as for an item without orphaned entries, and wrote a warning
for an item that it couldn't read; it now writes a `ReadSecurityError`,
like `Get-NTFSAudit`
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD [Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

8
Docs/Cmdlets/Get-NTFSOrphanedAudit.md

@ -163,7 +163,7 @@ You can pipe paths to this cmdlet, or objects that have a `Path` or `FullName` p
### Security2.FileSystemSecurity2[] ### Security2.FileSystemSecurity2[]
Security descriptors bind to the inherited `-SecurityDescriptor` parameter, but this cmdlet does not read their audit entries. Security descriptors that `Get-NTFSSecurityDescriptor` returned bind to `-SecurityDescriptor`, and the cmdlet examines their audit entries.
### Security2.IdentityReference2 ### Security2.IdentityReference2
@ -179,12 +179,14 @@ The cmdlet returns the audit entries whose account SID cannot be translated into
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message. When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it the cmdlet reads the security descriptor without its SACL and reports no orphaned entries at all, which looks the same as a tree that has none. Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it, the cmdlet writes the non-terminating error `ReadSecurityError` for each item, which reports "A required privilege is not held by the client", like `Get-NTFSAudit`.
If an item cannot be read, the cmdlet writes a warning and continues with the next item. Unlike `Get-NTFSAudit`, it does not try to take ownership of the item when access is denied. If the audit entries of an item can't be read, the cmdlet writes a `ReadSecurityError`, with the category `PermissionDenied` when access is denied, and continues with the next item; for a path that doesn't exist, it writes a `ReadError`. Like `Get-NTFSAudit`, it doesn't take ownership of the item, because ownership grants no access to the SACL.
Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor` and wrote the entries of each item as one collection. Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor` and wrote the entries of each item as one collection.
Before 5.0.0-rc7, the cmdlet read an item without its SACL when the Security privilege was missing and reported no orphaned entries, which looked the same as an item that has none. For an item that it couldn't read, it wrote a warning instead of an error.
## RELATED LINKS ## RELATED LINKS
[Get-NTFSAudit](Get-NTFSAudit.md) [Get-NTFSAudit](Get-NTFSAudit.md)

12
NTFSSecurity/AuditCmdlets/Get-OrphanedAudit.cs

@ -46,13 +46,21 @@ namespace NTFSSecurity.AuditCmdlets
IEnumerable<FileSystemAuditRule2> acl = null; IEnumerable<FileSystemAuditRule2> acl = null;
// Only the SACL, like Get-NTFSAudit, which fails without the Security privilege. Before 5.0.0-rc7, the
// cmdlet read the item without its audit entries then and returned nothing, as for an item without
// orphaned entries, and it wrote a warning for an item that it couldn't read.
try try
{ {
acl = FileSystemAuditRule2.GetFileSystemAuditRules(item, !ExcludeExplicit, !ExcludeInherited, getInheritedFrom); acl = GetAuditRules(item);
}
catch (UnauthorizedAccessException ex)
{
this.WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.PermissionDenied, p));
continue;
} }
catch (Exception ex) catch (Exception ex)
{ {
this.WriteWarning(string.Format("Could not read item {0}. The error was: {1}", p, ex.Message)); this.WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, p));
continue; continue;
} }

6
NTFSSecurity/AuditCmdlets/GetAudit.cs

@ -130,9 +130,11 @@ namespace NTFSSecurity
} }
} }
private IEnumerable<FileSystemAuditRule2> GetAuditRules(FileSystemInfo item) /// <summary>
/// Reads only the SACL of an item. Without the Security privilege, this fails instead of returning no entries.
/// </summary>
protected IEnumerable<FileSystemAuditRule2> GetAuditRules(FileSystemInfo item)
{ {
// Reading only the SACL fails without the Security privilege, instead of returning no entries.
var sd = new FileSystemSecurity2(item, System.Security.AccessControl.AccessControlSections.Audit); var sd = new FileSystemSecurity2(item, System.Security.AccessControl.AccessControlSections.Audit);
return FileSystemAuditRule2.GetFileSystemAuditRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom); return FileSystemAuditRule2.GetFileSystemAuditRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom);
} }

7
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -5988,7 +5988,7 @@ PS C:\&gt; Get-NTFSAudit -SecurityDescriptor $sd</dev:code>
<maml:name>Security2.FileSystemSecurity2[]</maml:name> <maml:name>Security2.FileSystemSecurity2[]</maml:name>
</dev:type> </dev:type>
<maml:description> <maml:description>
<maml:para>Security descriptors bind to the inherited `-SecurityDescriptor` parameter, but this cmdlet does not read their audit entries.</maml:para> <maml:para>Security descriptors that `Get-NTFSSecurityDescriptor` returned bind to `-SecurityDescriptor`, and the cmdlet examines their audit entries.</maml:para>
</maml:description> </maml:description>
</command:inputType> </command:inputType>
<command:inputType> <command:inputType>
@ -6013,9 +6013,10 @@ PS C:\&gt; Get-NTFSAudit -SecurityDescriptor $sd</dev:code>
<maml:alertSet> <maml:alertSet>
<maml:alert> <maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para> <maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it the cmdlet reads the security descriptor without its SACL and reports no orphaned entries at all, which looks the same as a tree that has none.</maml:para> <maml:para>Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it, the cmdlet writes the non-terminating error `ReadSecurityError` for each item, which reports "A required privilege is not held by the client", like `Get-NTFSAudit`.</maml:para>
<maml:para>If an item cannot be read, the cmdlet writes a warning and continues with the next item. Unlike `Get-NTFSAudit`, it does not try to take ownership of the item when access is denied.</maml:para> <maml:para>If the audit entries of an item can't be read, the cmdlet writes a `ReadSecurityError`, with the category `PermissionDenied` when access is denied, and continues with the next item; for a path that doesn't exist, it writes a `ReadError`. Like `Get-NTFSAudit`, it doesn't take ownership of the item, because ownership grants no access to the SACL.</maml:para>
<maml:para>Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor` and wrote the entries of each item as one collection.</maml:para> <maml:para>Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor` and wrote the entries of each item as one collection.</maml:para>
<maml:para>Before 5.0.0-rc7, the cmdlet read an item without its SACL when the Security privilege was missing and reported no orphaned entries, which looked the same as an item that has none. For an item that it couldn't read, it wrote a warning instead of an error.</maml:para>
</maml:alert> </maml:alert>
</maml:alertSet> </maml:alertSet>
<command:examples> <command:examples>

24
Tests/Audit.Tests.ps1

@ -242,7 +242,7 @@ Describe 'Get-NTFSOrphanedAudit' {
} }
} }
It 'Should write an error for a path that does not exist and continue with the next path' { It 'Should write an error for a path that does not exist and continue with the next path' -Skip:(-not $canReadAudit) {
$result = @(Get-NTFSOrphanedAudit -Path $missing, $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue) $result = @(Get-NTFSOrphanedAudit -Path $missing, $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
$orphanedErrors | Should -HaveCount 1 $orphanedErrors | Should -HaveCount 1
@ -251,6 +251,19 @@ Describe 'Get-NTFSOrphanedAudit' {
$result | ForEach-Object -Process { $_.FullName | Should -Be $orphanedFile } $result | ForEach-Object -Process { $_.FullName | Should -Be $orphanedFile }
} }
# Since 5.0.0-rc7, the next path has an error of its own without the Security privilege, which shows that the cmdlet
# continued with it.
It 'Should write an error for a path that does not exist and continue with the next path without the Security privilege' -Skip:$canReadAudit {
$result = @(Get-NTFSOrphanedAudit -Path $missing, $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$orphanedErrors | Should -HaveCount 2
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadError,*'
$orphanedErrors[0].TargetObject | Should -Be $missing
$orphanedErrors[1].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$orphanedErrors[1].TargetObject | Should -Be $orphanedFile
}
It 'Should write an error for a security descriptor that was read without the audit entries' { It 'Should write an error for a security descriptor that was read without the audit entries' {
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList ( $sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
(Get-Item2 -Path $orphanedFile), [System.Security.AccessControl.AccessControlSections]::Access (Get-Item2 -Path $orphanedFile), [System.Security.AccessControl.AccessControlSections]::Access
@ -263,12 +276,15 @@ Describe 'Get-NTFSOrphanedAudit' {
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*' $orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
} }
# The cmdlet page: without the privilege, the cmdlet reads no audit entries and reports none. # Before 5.0.0-rc7, the cmdlet read the item without its audit entries and returned nothing, as for an item without
It 'Should return nothing and write no error without the Security privilege' -Skip:$canReadAudit { # orphaned entries; it now writes the error of Get-NTFSAudit.
It 'Should write a ReadSecurityError without the Security privilege instead of returning nothing' -Skip:$canReadAudit {
$result = @(Get-NTFSOrphanedAudit -Path $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue) $result = @(Get-NTFSOrphanedAudit -Path $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
$orphanedErrors | Should -BeNullOrEmpty
$result | Should -BeNullOrEmpty $result | Should -BeNullOrEmpty
$orphanedErrors | Should -HaveCount 1
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$orphanedErrors[0].TargetObject | Should -Be $orphanedFile
} }
} }

Loading…
Cancel
Save