Monthly Archives: April 2017

ACLs Folder class

‘Sup PSHomies,

I recently had to make a quick Backup & Restore of ACLs three levels deep. Nothing fancy, just two functions, but that got me thinking…

Why not make a class of this?

And so I did! Here’s the framework for the class:

classACLFolders

Here’s a list of the methods:

  • Backup. Backup SDDL from the property $Folder of the class
  • Restore. Restore SDDL to the property $Folder of the class
  • Clone. Clone will take a target Folder and clone the SDDL of $Folder to it
  • ConvertSDDLToAccess. This will enumerate what the SDDL stands for

Default Constructor

classACLDefConstructor

The default constructor will evaluate the folder used to instantiate the object. If successful, the SDDL, Owner and Access is retrieved using  the Backup() method. All actions are registered for future reference.

Instantiating is pretty straightforward:

instantiateClass

Backup()

classACLBackup

This will retrieve the SDDL for the folder and enumerate the Access.

Restore()

classACLRestore

Restore is a bit tricky. For one you need to make sure it isn’t empty. Set-Acl has no problem setting an empty SDDL, blowing everything open (worst case scenario, and that’s why you should test this in a lab first!). The other challenge is having a valid SDDL string. You can change the SDDL string if you want to something gibberish, hence the typecast as a precaution.

Clone()

classACLDefClone

The same goes for cloning. In this case we need to test the target path. Alternatively, you could  also change the Folder to a new path… It works, you’d just have misleading ActionHistory entries… I wonder if there’s a way to make this read-only,  just thinking out loud here… (note to self)

ConvertSDDLToAccess()

This is just a lil’ something extra. Like I said in a previous blog SDDL really gives more information. For one, the SID will let you know which domain this object belongs to. One thing I ran into with ReACL is that SIDHistory will resolve to the current NTAccount. This had me puzzled for a while until I saw that the SIDs in SDDL where different.

Here’s what the ouput looks like:

convertSDDLToAccess

Now for those of you that are wondering just what is this AccessMask, wonder no more! πŸ™‚

Remember the RoboCopy ExitCodes blog I did a while back? Well it’s the same principal πŸ™‚ This is why classes & everything related should be on your radar…

enumAccessmask

Here’s how this works…

Say I wanted to evaluate the AccessMask of a SDDL entry

AccessMaskEnum
classACLConvertSDDLToAccess

Here I have the SID & the NTAccount. This is the builtin administrators account but it also works for Domain accounts.

There’s a private function that will translate the SID accordingly.

To see what the account can actually do we can enumerate the AccessMask

AccessMaskEnum1
AdvancedPermissions

This is what we’d see using the advanced Security GUI tab of a folder.

Not bad… Not bad at all…

I can’t state this enough, SDDL is NOT to be be trifled with. Yes you need admin rights and please try this is a testlab first.  SDDL is very potent, if used with caution, it could do a whole bit of good!

So finally, here’s the code…

[Flags()] Enum AccessMask{
Read = 1
Write = 2
Append = 4
ReadExtendedAttributes = 8
WriteExtendedAttributes = 16
Execute = 32
DeleteDirectory = 64
ReadAttributes = 128
WriteAttributes = 256
Delete = 65536
ReadSecurity = 131072
WriteACL = 262144
WriteOwner = 524288
Synchronize = 1048576
}
class aclsFolder{
[String]$Folder
[String]$SDDL
[String]$Owner
[PSObject[]]$Access
[PSObject[]]$ActionHistory
#Default Constructor
aclsFolder($fldr){
if(Test-Path -Path $fldr){
$this.Folder = $fldr
$Tags = @('Default','Constructor','valid')
$MessageData = "Path $($fldr) found"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
$this.Backup()
}
else{
$Tags = @('Default','Constructor','invalid')
$MessageData = "Path $($fldr) not found"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
}
#Methods
Backup(){
if(Test-Path $this.Folder){
$result = Get-Acl $this.Folder
$this.SDDL = $result.Sddl
$this.Owner = $result.Owner
$this.Access = $($result.Access | Select-Object File*,Access*,Identity*,IsInherited,*Flags)
$Tags = @('Backup','Success')
$MessageData = "Backup SDDL of $($this.Folder) was successful"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
else{
Write-Warning "Invalid Path $($this.Folder)"
$Tags = @('Backup','Failed')
$MessageData = "Backup SDDL of $($this.Folder) has failed"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
}
Restore(){
if((Test-Path $this.Folder) -and
![string]::isNullOrEmpty($this.SDDL) -and
[Security.AccessControl.RawSecurityDescriptor]$this.SDDL){
$acl = Get-Acl -Path $this.Folder
$acl.SetSecurityDescriptorSddlForm($this.SDDL)
Set-Acl -Path $($this.Folder) -AclObject $($acl)
$Tags = @('Restore', 'Success')
$MessageData = "Restoring SDDL on $($this.Folder) was succesful"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
#Reset values
$this.Backup()
}
else{
Write-Warning "Invalid Path $($this.Folder) or SDDL is invalid"
$Tags = @('Restore', 'Failed')
$MessageData = "Restoring SDDL on $($this.Folder) has failed"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
}
Clone($tgt){
if((Test-Path -Path $tgt) -and
![string]::isNullOrEmpty($this.SDDL) -and
[Security.AccessControl.RawSecurityDescriptor]$this.SDDL){
$acl = Get-Acl -Path $tgt
$acl.SetSecurityDescriptorSddlForm($this.SDDL)
Set-Acl -Path $($tgt) -AclObject $($acl)
$Tags = @('Clone', 'Success')
$MessageData = "Cloning SDDL on $($this.Folder) was succesful"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
else{
Write-Warning "Invalid Path $($this.Folder) or SDDL is invalid"
$Tags = @('Clone', 'Failed')
$MessageData = "Cloning SDDL on $($this.Folder) has failed"
$this.ActionHistory += Write-Information -MessageData $MessageData 6>&1 -Tags $Tags | Select-Object *
}
}
[PSObject]ConvertSDDLToAccess(){
Function Convert-SID2NTAccount{
param(
$SID
)
$objSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
$objUser
}
$accessSDDL = ([Security.AccessControl.RawSecurityDescriptor]$this.SDDL).DiscretionaryAcl |
ForEach-Object{
[PSCustomObject]@{
SID = $_.SecurityIdentifier
NTAccount = (Convert-SID2NTAccount -SID $_.SecurityIdentifier)
AceQualifier = $_.AceQualifier
AccessMask = $_.AccessMask
AceType = $_.AceType
AceFlags = $_.AceFlags
IsInherited = $_.IsInherited
InheritanceFlags = $_.InheritanceFlags
}
}
return $accessSDDL
}
}

Hope it’s worth something to you…

Ttyl,

Urv