The other day, I got a comment on an old post asking about the status of using conditional ACEs (something I said in the post that I was planning to support in the PAC module). Over the past few nights, I played around with parsing and creating them. What I have so far is not even close to being finished, but I thought I might share it to see if there’s any interest in trying to do more with it.
First, what is a conditional ACE? It’s an ACE that is only effective in an ACL if a certain condition is met. For instance, maybe you want to allow Users to have Modify rights to a shared folder if the computer they’re using is a member of ‘Domain Controllers’ (that’s not a very good example, but you should be able to create that condition out of the box for a Server 2012 R2 or higher computer in a test domain without any extra work). Here’s what that would look like in the GUI and in SDDL form:
The conditions can get A LOT more specific (and complicated) than that, too. If you do some setup in your domain, you can actually have conditions check certain “claims” that apply to users, devices, and/or resources. Scenarios then become available where certain files (resources) can be dynamically classified (a separate technology) to only allow access from users/devices that meet certain conditions. Conditions like being in a certain department, or from a certain country (defined in Active Directory). I don’t want to spend too much time on explaining this because I would probably do such a bad job that it would turn you away from wanting to look into it any more.
Back to the simple example from the screenshot above: besides using the GUI and knowing how to write that SDDL by hand, I haven’t been able to find another way to create those conditions. The .NET Framework is able to give you the binary form of the condition, but that’s about it. The binary format is documented pretty well here, though, so I took that and messed around with some proof of concept code to parse and create the conditions. That code can be found in this GIST. Please note the following about it:
- It’s meant to be used with Add-Type in PowerShell
- I’m not really a developer, so that’s definitely not the prettiest and most efficient code. It’s going to change A LOT, too. Now that I have a better understanding of the binary format of the conditions (I hope), I’ll probably try to come up with a better design. If you have any suggestions, let me know.
- There are still conditions this can’t handle. Non-Unicode encoded string tokens and numeric tokens aren’t supported yet. They’re coming, though…
- The text form of the conditions is different that what the GUI shows. I’m playing around with making it closer to what you’d see with PowerShell, e.g., ‘-eq’ instead of ‘==’, ‘-and’ instead of ‘&&’, etc. I plan on having the text represenation being configurable so that you can have the GUI version, the SDDL version, or the PAC module’s version displayed.
- Please only use it in a test environment.
Now that that’s out of the way, let’s go over some examples. First, how can you read this stuff? If you use the PAC module 4.0.82.20150706 or earlier, you’ll get something that looks like this:
That’s not very helpful. The only indication that the conditional ACE is special is the ‘(CB)’ at the end of the AceType column (that stands for Callback). There is hope, though! If you’d like to read conditions right now, you can try something like this (PAC module is required)…
# Add C# code from here: https://gist.github.com/rohnedwards/b5e7ca34a062d765bf4a Add-Type -Path C:\path\to\code\from\gist.cs Get-PacAccessControlEntry c:\folder | Add-Member -MemberType ScriptProperty -Name Condition -Value { $Ace = $this.GetBaseAceObject() if ($Ace.IsCallback) { [Testing.ConditionalAceCondition]::GetConditionalAceCondition($Ace.GetOpaque()) } } -PassThru | tee -var Aces | select AceType, Principal, AccessMask, InheritedFrom, AppliesTo, Condition | Out-GridView
… and get someting that looks like this:
What if you want to add a conditional ACE? That’s actually pretty nasty right now. Besides being forced to create your own condition and ACE using C# classes, I think you also have to add your new ACE with the RawSecurityDescriptor class, which means you are responsible for the position in the DACL where the ACE ends up. It can be done, though: (The PAC module isn’t needed for this; you do need the code from the GIST above, though)
First, let’s create the condition from the simple example above:
Add-Type -Path C:\path\to\code\from\gist.cs # Create an operator token: $Operator = New-Object Testing.ConditionalAceOperatorToken "Device_member_Of" # Device_member_Of is a unary operator, so create a unary condition with # the $Operator $Condition = New-Object Testing.ConditionalAceUnaryCondition $Operator # This unary condition needs an array of SID tokens. In our example, we have a single # SID we're using, so let's look that up first: $DcGroupSid = ([System.Security.Principal.NTAccount] "Domain Controllers").Translate([System.Security.Principal.SecurityIdentifier]) # Then create a composite token, which is going to contain the list of SID tokens: $CompositeToken = New-Object Testing.ConditionalAceCompositeToken # Then add a SID token to the composite token: $CompositeToken.Tokens.Add((New-Object Testing.ConditionalAceSecurityIdentifierToken $DcGroupSid)) # Finally, assign the operand $Condition.Operand = New-Object Testing.ConditionalAceConditionalLiteralOperand $CompositeToken
Next, let’s create an ACE with that condition:
$NewAce = New-Object System.Security.AccessControl.CommonAce ( "ContainerInherit, ObjectInherit", # ACE flags [System.Security.AccessControl.AceQualifier]::AccessAllowed, [System.Security.AccessControl.FileSystemRights]::Modify, ([ROE.PowerShellAccessControl.PacPrincipal] "Users").SecurityIdentifier, $true, $Condition.GetApplicationData() )
And, finally, let’s add the ACE to the DACL:
$Path = "C:\folder" $Acl = Get-Acl $Path $RawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor $Acl.Sddl # Figure out where the ACE should go (this is to preserve canonical ordering; I # didn't think much about this, so this might not always work): for ($i = 0; $i -lt $RawSD.DiscretionaryAcl.Count; $i++) { $CurrentAce = $RawSD.DiscretionaryAcl[$i] if ($CurrentAce.IsInherited -or $CurrentAce.AceQualifier.ToString() -eq "AccessAllowed") { break } } $RawSD.DiscretionaryAcl.InsertAce($i, $NewAce) # Save to SD and write it back to folder $Acl.SetSecurityDescriptorSddlForm($RawSD.GetSddlForm("All")) (Get-Item $Path).SetAccessControl($Acl)
And we’re done! Don’t worry, this shouldn’t always be this hard. Some cmdlets to create conditions will help a lot. Also, the PAC module’s New-PacAccessControlEntry, Add-PacAccessControlEntry, and Remove-PacAccessControlEntry commands should know how to add these ACEs one day.
So, is this useful to anyone, and should I spend time trying to get the PAC module to handle this? Are there any scenarios you have that you’d like to see an example for? Please leave a comment and/or contact me on Twitter (@magicrohn) if so.