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.
238 lines
9.0 KiB
238 lines
9.0 KiB
<#
|
|
Helpers of the live tests in a lab (README.md). NTFSSecurity.Live.Tests.ps1 dot-sources this file on the client
|
|
and on the file server, and Invoke-NTFSSecurityLabTest.ps1 runs it on both to prepare the fixtures. Dot-sourcing
|
|
it only defines the helpers.
|
|
#>
|
|
|
|
if (-not ('NTFSSecurityLab.NativeMethods' -as [type])) {
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace NTFSSecurityLab
|
|
{
|
|
// GetFileSecurity and SetFileSecurity read and write a security descriptor as Windows stores it.
|
|
// GetNamedSecurityInfo and SetNamedSecurityInfo, which .NET and the module use, convert a DACL without the
|
|
// auto-inherit flag when they read it, and add the flag when they write a DACL.
|
|
public static class NativeMethods
|
|
{
|
|
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
private static extern bool GetFileSecurityW(string fileName, int requestedInformation, byte[] securityDescriptor, int length, out int lengthNeeded);
|
|
|
|
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
private static extern bool SetFileSecurityW(string fileName, int securityInformation, byte[] securityDescriptor);
|
|
|
|
public static byte[] GetFileSecurity(string path, int information)
|
|
{
|
|
int needed;
|
|
GetFileSecurityW(path, information, null, 0, out needed);
|
|
if (needed == 0)
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
var buffer = new byte[needed];
|
|
if (!GetFileSecurityW(path, information, buffer, buffer.Length, out needed))
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
public static void SetFileSecurity(string path, int information, byte[] securityDescriptor)
|
|
{
|
|
if (!SetFileSecurityW(path, information, securityDescriptor))
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
}
|
|
|
|
function Get-LabSecurityDescriptor {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the owner, the group, and the DACL of a file or folder as Windows stores them.
|
|
.DESCRIPTION
|
|
GetNamedSecurityInfo returns the owner with a DACL without the auto-inherit flag even when only the DACL is
|
|
read, and converts such a DACL. This function reads with GetFileSecurity, which does neither.
|
|
.PARAMETER Path
|
|
A local path or a UNC path. The account needs the right to read the permissions.
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([System.Security.AccessControl.RawSecurityDescriptor])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string]
|
|
$Path
|
|
)
|
|
|
|
# OWNER_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, and DACL_SECURITY_INFORMATION
|
|
$bytes = [NTFSSecurityLab.NativeMethods]::GetFileSecurity($Path, 7)
|
|
New-Object -TypeName 'System.Security.AccessControl.RawSecurityDescriptor' -ArgumentList $bytes, 0
|
|
}
|
|
|
|
function Test-LabDaclAutoInherited {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns whether the stored DACL of a file or folder has the auto-inherit flag.
|
|
.PARAMETER Path
|
|
A local path or a UNC path.
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([bool])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string]
|
|
$Path
|
|
)
|
|
|
|
$autoInherited = [System.Security.AccessControl.ControlFlags]::DiscretionaryAclAutoInherited
|
|
((Get-LabSecurityDescriptor -Path $Path).ControlFlags -band $autoInherited) -ne 0
|
|
}
|
|
|
|
function Set-LabLegacyDacl {
|
|
<#
|
|
.SYNOPSIS
|
|
Stores the DACL of a file or folder again, without the auto-inherit flag.
|
|
.DESCRIPTION
|
|
Windows adds the flag whenever SetNamedSecurityInfo writes a DACL. SetFileSecurity stores the DACL as given,
|
|
like tools that predate Windows 2000. For such a DACL, GetNamedSecurityInfo returns the owner also when only
|
|
the DACL is read, and NTFSSecurity before 5.0.0-rc3 wrote that owner back (#34).
|
|
.PARAMETER Path
|
|
A local path. The account needs the right to change the permissions.
|
|
#>
|
|
[CmdletBinding(SupportsShouldProcess)]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string]
|
|
$Path
|
|
)
|
|
|
|
$descriptor = Get-LabSecurityDescriptor -Path $Path
|
|
$autoInherited = [System.Security.AccessControl.ControlFlags]::DiscretionaryAclAutoInherited
|
|
$descriptor.SetFlags([System.Security.AccessControl.ControlFlags]($descriptor.ControlFlags -band -bnot $autoInherited))
|
|
$bytes = New-Object -TypeName 'byte[]' -ArgumentList $descriptor.BinaryLength
|
|
$descriptor.GetBinaryForm($bytes, 0)
|
|
if ($PSCmdlet.ShouldProcess($Path, 'Store the DACL without the auto-inherit flag')) {
|
|
# DACL_SECURITY_INFORMATION
|
|
[NTFSSecurityLab.NativeMethods]::SetFileSecurity($Path, 4, $bytes)
|
|
}
|
|
}
|
|
|
|
function Get-LabTokenSid {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the SIDs of the token that this computer creates for a domain account.
|
|
.DESCRIPTION
|
|
Logs the account on with Kerberos S4U, without its password, like the Effective Access tab of the advanced
|
|
security settings does. The token holds the account and all its groups that this computer knows: nested
|
|
domain groups and the local groups of this computer.
|
|
.PARAMETER UserPrincipalName
|
|
The user principal name of the account, such as user@contoso.com.
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([string])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string]
|
|
$UserPrincipalName
|
|
)
|
|
|
|
$identity = New-Object -TypeName 'System.Security.Principal.WindowsIdentity' -ArgumentList $UserPrincipalName
|
|
try {
|
|
$identity.User.Value
|
|
foreach ($group in $identity.Groups) {
|
|
$group.Value
|
|
}
|
|
}
|
|
finally {
|
|
$identity.Dispose()
|
|
}
|
|
}
|
|
|
|
function Get-LabGrantedRight {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the access mask that the allow entries of a DACL grant to a set of SIDs.
|
|
.DESCRIPTION
|
|
Combines the entries that apply to the item itself and name one of the SIDs. A deny entry for one of the SIDs
|
|
makes the function throw, because the calculation doesn't cover it.
|
|
.PARAMETER Descriptor
|
|
The security descriptor of the item.
|
|
.PARAMETER Sid
|
|
The SIDs of a token, such as the output of Get-LabTokenSid.
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([long])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[System.Security.AccessControl.RawSecurityDescriptor]
|
|
$Descriptor,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string[]]
|
|
$Sid
|
|
)
|
|
|
|
$granted = 0L
|
|
foreach ($ace in $Descriptor.DiscretionaryAcl) {
|
|
if ($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.SecurityIdentifier.Value -notin $Sid) {
|
|
continue
|
|
}
|
|
|
|
# HasFlag, because Windows PowerShell can't apply -band to an enum of the type byte.
|
|
if ($ace.AceFlags.HasFlag([System.Security.AccessControl.AceFlags]::InheritOnly)) {
|
|
continue
|
|
}
|
|
|
|
if ($ace.AceQualifier -ne [System.Security.AccessControl.AceQualifier]::AccessAllowed) {
|
|
throw "The DACL denies $($ace.SecurityIdentifier) access, which Get-LabGrantedRight doesn't calculate."
|
|
}
|
|
|
|
$granted = $granted -bor ([long]$ace.AccessMask -band 0xFFFFFFFFL)
|
|
}
|
|
|
|
$granted
|
|
}
|
|
|
|
function Assert-LabTestTarget {
|
|
<#
|
|
.SYNOPSIS
|
|
Throws unless this process runs on the client or the file server that a configuration of the lab names, and
|
|
the folder of the run lies in the share of the lab.
|
|
.DESCRIPTION
|
|
The live tests change security descriptors and must never run on another computer, such as the host of the
|
|
lab or a workstation.
|
|
.PARAMETER Configuration
|
|
The configuration of the run that Invoke-NTFSSecurityLabTest.ps1 wrote.
|
|
#>
|
|
[CmdletBinding()]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[object]
|
|
$Configuration
|
|
)
|
|
|
|
# Without CIM, which an account that isn't an administrator can't use in a remote session
|
|
$domainName = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName
|
|
if ($domainName -ne $Configuration.DomainName) {
|
|
throw "The live tests run only on a computer of the lab domain '$($Configuration.DomainName)'."
|
|
}
|
|
|
|
if ($env:COMPUTERNAME -notin $Configuration.Client, $Configuration.FileServer) {
|
|
throw "The live tests run only on '$($Configuration.Client)' and '$($Configuration.FileServer)', not on '$env:COMPUTERNAME'."
|
|
}
|
|
|
|
$shareRoot = '\\{0}\{1}\' -f $Configuration.FileServerFqdn, $Configuration.ShareName
|
|
$comparison = [System.StringComparison]::OrdinalIgnoreCase
|
|
if (-not $Configuration.SharePath.StartsWith($shareRoot, $comparison) -or
|
|
-not $Configuration.ServerPath.StartsWith($Configuration.ShareLocalPath + '\', $comparison) -or
|
|
$Configuration.SharePath.Contains('..') -or $Configuration.ServerPath.Contains('..')) {
|
|
throw "The folder of the run must lie in the share '$shareRoot' of the lab."
|
|
}
|
|
}
|
|
|