Helpers/SddlUtils.ps1
|
function Get-AceFlagsFromInheritanceAndPropagation { [OutputType([System.Security.AccessControl.AceFlags])] param ( [Parameter(Mandatory = $true)] [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags, [Parameter(Mandatory = $true)] [System.Security.AccessControl.PropagationFlags]$PropagationFlags ) $aceFlags = [System.Security.AccessControl.AceFlags]::None if ($InheritanceFlags -band [System.Security.AccessControl.InheritanceFlags]::ContainerInherit) { $aceFlags = ([int]$aceFlags -bor [int][System.Security.AccessControl.AceFlags]::ContainerInherit) } if ($InheritanceFlags -band [System.Security.AccessControl.InheritanceFlags]::ObjectInherit) { $aceFlags = ([int]$aceFlags -bor [int][System.Security.AccessControl.AceFlags]::ObjectInherit) } if ($PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly) { $aceFlags = ([int]$aceFlags -bor [int][System.Security.AccessControl.AceFlags]::InheritOnly) } if ($PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::NoPropagateInherit) { $aceFlags = ([int]$aceFlags -bor [int][System.Security.AccessControl.AceFlags]::NoPropagateInherit) } return $aceFlags } function Get-AllAceFlagsMatch { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$SecurityDescriptor, [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$EnabledFlags, [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$DisabledFlags ) process { foreach ($ace in $SecurityDescriptor.DiscretionaryAcl) { $hasAllBitsFromEnabledFlags = ([int]$ace.AceFlags -band [int]$EnabledFlags) -eq [int]$EnabledFlags $hasNoBitsFromDisabledFlags = ([int]$ace.AceFlags -band [int]$DisabledFlags) -eq 0 if (-not ($hasAllBitsFromEnabledFlags -and $hasNoBitsFromDisabledFlags)) { return $false } } return $true } } function Set-AceFlags { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "We are setting the AceFlags property.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( "PSUseShouldProcessForStateChangingFunctions", "", Justification = "No external side effects, just changes the security descriptor object in-place. So no real value to supporting -WhatIf.")] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$SecurityDescriptor, [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$EnableFlags, [Parameter(Mandatory = $true)] [System.Security.AccessControl.AceFlags]$DisableFlags ) begin { if ([int]$EnableFlags -band [int]$DisableFlags) { throw "Enable and disable flags cannot overlap" } } process { # Create new ACEs with updated flags $newAces = $SecurityDescriptor.DiscretionaryAcl | ForEach-Object { $aceFlags = ([int]$_.AceFlags -bor [int]$EnableFlags) -band (-bnot [int]$DisableFlags) if ($_.GetType().Name -eq "CommonAce") { [System.Security.AccessControl.CommonAce]::new( $aceFlags, $_.AceQualifier, $_.AccessMask, $_.SecurityIdentifier, $_.IsCallback, $_.GetOpaque()) } else { throw "Unsupported ACE type: $($_.GetType().Name)" } } # Remove all old ACEs for ($i = $SecurityDescriptor.DiscretionaryAcl.Count - 1; $i -ge 0; $i--) { $SecurityDescriptor.DiscretionaryAcl.RemoveAce($i) | Out-Null } # Add all new ACEs for ($i = 0; $i -lt $newAces.Count; $i++) { $SecurityDescriptor.DiscretionaryAcl.InsertAce($i, $newAces[$i]) | Out-Null } } } function Get-EmptyRawAcl { $revision = [AclRevision]::ACL_REVISION $capacity = 0 return [System.Security.AccessControl.RawAcl]::new([int]$revision, $capacity) } function Reset-SecurityDescriptor { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Low')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.RawSecurityDescriptor]$SecurityDescriptor ) process { $flagsToRemove = [System.Security.AccessControl.ControlFlags]::DiscretionaryAclAutoInherited -bor ` [System.Security.AccessControl.ControlFlags]::DiscretionaryAclProtected -bor ` [System.Security.AccessControl.ControlFlags]::SystemAclAutoInherited -bor ` [System.Security.AccessControl.ControlFlags]::SystemAclProtected -bor ` [System.Security.AccessControl.ControlFlags]::DiscretionaryAclPresent -bor ` [System.Security.AccessControl.ControlFlags]::SystemAclPresent $controlFlags = [int]$SecurityDescriptor.ControlFlags -band (-bnot [int]$flagsToRemove) if ($PSCmdlet.ShouldProcess("SecurityDescriptor", "Reset ControlFlags, DACL and SACL")) { $SecurityDescriptor.DiscretionaryAcl = Get-EmptyRawAcl $SecurityDescriptor.SystemAcl = Get-EmptyRawAcl $SecurityDescriptor.SetFlags($controlFlags) } } } class AccessMask { [int]$Value AccessMask([int]$mask) { $this.Value = $this.Normalize($mask) } [int]Normalize([int]$mask) { return $mask -band 0xFFFFFFFF } [bool]Has([int]$permission) { return ($this.Value -band $permission) -eq $permission } [Void]Add([int]$permission) { $this.Value = $this.Value -bor $permission } [Void]Remove([int]$permission) { $this.Value = $this.Value -band -bnot $permission } } function Write-SecurityDescriptor { <# .SYNOPSIS Displays a detailed, formatted view of a security descriptor including owner, group, control flags, and ACLs. .DESCRIPTION The Write-SecurityDescriptor function provides a comprehensive, human-readable display of a security descriptor's components It displays the owner, group, control flags, discretionary ACL (DACL), and system ACL (SACL) with color-coded formatting for enhanced readability. This function is particularly useful for debugging, auditing, and understanding the structure of Windows security descriptors. .PARAMETER Acl Specifies the security descriptor or ACL to display. This can be in various formats including SDDL (Security Descriptor Definition Language) string, base64-encoded binary, array of bytes, CommonSecurityDescriptor or RawSecurityDescriptor objects. .PARAMETER AclFormat Specifies the format of the input ACL. If not provided, the function will automatically infer the format. Supported formats include SDDL, Base64, Binary, and Raw. .OUTPUTS System.Void This function outputs formatted text to the console and does not return any objects. .EXAMPLE PS> $acl = "O:BAG:SYD:(A;;FA;;;SY)(A;;0x1200a9;;;BU)" PS> Write-SecurityDescriptor -Acl $acl -AclFormat Sddl Displays a formatted view of the SDDL security descriptor, showing owner, group, control flags, and both discretionary and system ACLs with detailed access mask information. .EXAMPLE PS> $context = Get-AzStorageContext -StorageAccountName "mystorageaccount" -StorageAccountKey "mykey" PS> Get-AzFileAcl -Context $context -FileShareName "myshare" -FilePath "folder/file.txt" | Write-SecurityDescriptor .LINK Get-AzFileAcl .LINK Convert-SecurityDescriptor #> param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [object]$Acl, [Parameter(Mandatory = $false)] [SecurityDescriptorFormat]$AclFormat ) process { if ($null -eq $AclFormat) { $AclFormat = Get-InferredAclFormat $Acl Write-Verbose "Inferred ACL format: $AclFormat. To override, use -AclFormat." } $descriptor = Convert-SecurityDescriptor $Acl -From $AclFormat -To Raw $controlFlagsHex = "0x{0:X}" -f [int]$descriptor.ControlFlags Write-Host "Owner: $($PSStyle.Foreground.Cyan)$($descriptor.Owner)$($PSStyle.Reset)" Write-Host "Group: $($PSStyle.Foreground.Cyan)$($descriptor.Group)$($PSStyle.Reset)" Write-Host "ControlFlags: $($PSStyle.Foreground.Cyan)$controlFlagsHex$($PSStyle.Reset) ($($descriptor.ControlFlags))" Write-Host "DiscretionaryAcl:" Write-Acl $descriptor.DiscretionaryAcl -indent 4 Write-Host "SystemAcl:" Write-Acl $descriptor.SystemAcl -indent 4 } } function Write-Acl { param ( [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true)] [System.Security.AccessControl.RawAcl]$acl, [Parameter(Mandatory = $false)] [int]$indent = 0 ) begin { $spaces = " " * $indent } process { if ($acl -eq $null) { Write-Host "${spaces}Not present" return } Write-Host "${spaces}Revision: $($($PSStyle.Foreground.Cyan))$($acl.Revision)$($PSStyle.Reset)" Write-Host "${spaces}BinaryLength: $($($PSStyle.Foreground.Cyan))$($acl.BinaryLength)$($PSStyle.Reset)" Write-Host "${spaces}AceCount: $($($PSStyle.Foreground.Cyan))$($acl.Count)$($PSStyle.Reset)" $i = 0 foreach ($ace in $acl) { Write-Host "${spaces}Ace $($PSStyle.Foreground.Green)${i}$($PSStyle.Reset):" Write-Ace $ace -indent ($indent + 4) $i++ } } } function Write-Ace { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Security.AccessControl.GenericAce]$ace, [Parameter(Mandatory = $false)] [int]$indent = 0 ) begin { $spaces = " " * $indent } process { $aceTypeHex = "0x{0:X}" -f [int]$ace.AceType $aceSizeHex = "0x{0:X}" -f [int]$ace.BinaryLength $aceFlagsHex = "0x{0:X}" -f [int]$ace.AceFlags $accessMaskHex = "0x{0:X}" -f [int]$ace.AccessMask Write-Host "${spaces}Ace Sid: $($PSStyle.Foreground.Cyan)$($ace.SecurityIdentifier)$($PSStyle.Reset)" Write-Host "${spaces}AceType: $($PSStyle.Foreground.Cyan)$aceTypeHex$($PSStyle.Reset) ($($ace.AceType))" Write-Host "${spaces}AceSize: $($PSStyle.Foreground.Cyan)$aceSizeHex$($PSStyle.Reset) ($($ace.BinaryLength))" Write-Host "${spaces}AceFlags: $($PSStyle.Foreground.Cyan)$aceFlagsHex$($PSStyle.Reset) ($($ace.AceFlags))" Write-Host "${spaces}Access Mask: $($PSStyle.Foreground.Cyan)$accessMaskHex$($PSStyle.Reset) ($($ace.AccessMask))" Write-AccessMask $ace.AccessMask -indent ($indent + 4) } } function Write-AccessMask { <# .SYNOPSIS Displays a detailed, formatted view of a ACE's access mask. .DESCRIPTION The Write-AccessMask function provides a comprehensive, human-readable display of an ACE's access mask. .PARAMETER AccessMask Specifies the access mask to display. .PARAMETER ShowFullList If specified, the function will display the full list of individual permission bits in addition to the basic permissions. .OUTPUTS System.Void This function outputs formatted text to the console and does not return any objects. .EXAMPLE PS> Write-AccessMask 0x1200a9 #> param ( [Parameter(Mandatory = $true, Position = 0)] [int]$accessMask, [Parameter(Mandatory = $false)] [int]$indent = 0, [Parameter(Mandatory = $false)] [switch]$ShowFullList = $false ) # Convert generics to specifics $accessMask = Get-MappedAccessMask -AccessMask $accessMask $spaces = " " * $indent $mask = [AccessMask]::new($accessMask) $checkmark = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("2713", 16)) $cross = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32("2717", 16)) if ($ShowFullList) { Write-Host "${spaces}simplified list:" } # Write "basic permissions" first (e.g. composite rights like "Read", "Write", "Modify", etc.) $checkedValues = 0 foreach ($key in [Enum]::GetValues([BasicPermissions])) { $value = $key.value__ if ($mask.Has($value)) { $checkedValues = $checkedValues -bor $value Write-Host "${spaces}$($PSStyle.Foreground.Green)$checkmark$($PSStyle.Reset) $key" } else { Write-Host "${spaces}$($PSStyle.Foreground.Red)$cross$($PSStyle.Reset) $key" } } # Write if there are any permissions not covered by basic $remaining = [AccessMask]::new($accessMask) $remaining.Remove($checkedValues) if ($remaining.Value -ne 0) { # Check what known values remain, in addition to the values already checked above $allValues = [Enum]::GetValues([SpecificRights]) + [Enum]::GetValues([StandardRights]) + [Enum]::GetValues([GenericRights]) $remainingValueList = $allValues | Where-Object { $remaining.Has($_.value__) } # If there are any bits not covered by the known permissions, add it to the list $remainingValueList | ForEach-Object { $remaining.Remove($_.value__) } if ($remaining.Value -ne 0) { $remainingValueList += [string]::Format("0x{0:X}", $remaining.Value) } $remainingString = $remainingValueList -join ", " Write-Host "${spaces}$($PSStyle.Foreground.Green)$checkmark$($PSStyle.Reset) SPECIAL_PERMISSIONS ($remainingString)" } else { Write-Host "${spaces}$($PSStyle.Foreground.Red)$cross$($PSStyle.Reset) SPECIAL_PERMISSIONS" } # Optionally write the full list of permissions bits if ($ShowFullList) { Write-Host "${spaces}full list:" foreach ($key in [Enum]::GetValues([SpecificRights]) + [Enum]::GetValues([StandardRights]) + [Enum]::GetValues([GenericRights])) { $value = $key.value__ if ($mask.Has($value)) { Write-Host "${spaces} ${key}" $mask.Remove($value) } } if ($mask.Value -ne 0) { $hex = "0x{0:X}" -f $mask.Value Write-Host "${spaces} Others: $hex" } } } |