Posts Tagged ‘DACL’

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:
conditional_ace_gui

 

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:
pac_module_callback_ace
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:
get-ace_with_conditions

 

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.
Advertisements

There’s a new version of the PAC 4.0 Preview available on the TechNet Script Repository. There’s still no official documentation in the new version, so I’ll briefly mention some of the changes below. If you missed it, the first post on the 4.0 preview is here:

Modification Cmdlets

The following cmdlets are now available:

  • New-AccessControlEntry
  • Add-AccessControlEntry
  • Remove-AccessControlEntry
  • Enable-AclInheritance
  • Disable-AclInheritance
  • Set-Owner
  • Set-SecurityDescriptor

Like in previous versions, these commands can be used to work with native .NET security descriptor objects (output from Get-Acl), PAC security descriptor objects (output from Get-SecurityDescriptor), or directly with a whole bunch of objects. Here are some examples of what I’m talking about:

Working with .NET Security Descriptor Objects

You’re probably familiar with using the native PowerShell and .NET commands to work with security descriptors. You do something like this:

$Acl = Get-Acl C:\powershell
$Ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
 "Everyone",
 "Write",
 "ContainerInherit, ObjectInherit",
 "None",
 "Allow"
)
$Acl.AddAccessRule($Ace)
$Acl | Set-Acl

That’s a lot of work to add a single Allow ACE giving Everyone Write access. You can use the PAC module to shorten that code to this:


$Acl = Get-Acl C:\powershell
$Ace = New-AccessControlEntry -Principal Everyone -FolderRights Write
$Acl.AddAccessRule($Ace)
$Acl | Set-Acl


You can also just cut out the New-AccessControlEntry call completely, which would shorten the snippet to this:


$Acl = Get-Acl C:\powershell
$Acl | Add-AccessControlEntry -Principal Everyone -FolderRights Write
$Acl | Set-Acl


And finally, one more way to shorten that:


Get-Acl C:\powershell | Add-AccessControlEntry -Principal Everyone -FolderRights Write -Apply

When you use -Apply like that, the module will actually call Set-SecurityDescriptor, so you’re not just using native PowerShell and .NET commands at that point.

Working with PAC Security Descriptor Objects

This actually looks just like working with the .NET security descriptor objects, except you use Get-SecurityDescriptor instead of Get-Acl, and Set-SecurityDescriptor instead of Set-Acl.

Working With Objects Directly

You don’t even need to use Get-Acl/Set-Acl or Get-SecurityDescriptor/Set-SecurityDescriptor. There are a ton of .NET and WMI instances that the module knows how to work with. These commands would be valid:


# This defaults to enabling inheritance on the DACL, but the SACL can be controlled, too
dir C:\powershell -Recurse |
Enable-AclInheritance -PassThru |
Remove-AccessControlEntry -RemoveAllAccessEntries -Apply

# -Apply isn't necessary here because the input object isn't a security descriptor. -Force
# would stop it from prompting you before saving the security descriptor.
Get-Service bits | Add-AccessControlEntry -Principal Users -ServiceRights Start, Stop

Get-SmbShare share | Add-AccessControlEntry -Principal Everyone -AccessMask ([ROE.PowerShellAccessControl.Enums.ShareRights]::FullControl)

PacSDOption Common Parameter

Most of the commands in the module have a parameter named -PacSDOption. That’s how you control things like recursing through child items (where supported), getting the SACL, bypassing the ACL check (the -BypassAclCheck parameter from the last post doesn’t exist as a direct cmdlet parameter anymore). The parameter’s input is from the New-PacCommandOption cmdlet. Here’s an example:


# Get the DACL and SACL entries for C:\powershell, even if you don't have permission to view them
Get-AccessControlEntry C:\powershell -PacSDOption (New-PacCommandOption -BypassAclCheck -Audit)

# Get the DACL and SACL entries for C:\powershell and any child folders (even if long paths are there):
Get-AccessControlEntry C:\powershell -PacSDOption (New-PacCommandOption -Recurse -Directory)

Formatting

The default formatting of a security descriptor now shows both the DACL and the SACL:

new_getsecuritydescriptor_format

The module will also check for the existence of a hash table named $PacOptions, and change how ACEs are displayed depending on its value. For now, there’s a single display option ‘DontAbbreviateAppliesTo’ that let’s you control how the AppliesTo column is displayed on ACEs. Here’s an example of how to create the hash table and change the AppliesTo setting:

dontabbreviateappliesto

Remember that this is still a preview version, so you’ll probably come across some things that don’t work the way they’re supposed to. If you find a problem, have a question about how to do something, or have a suggestion, please either post a comment below or send me an e-mail (magicrohn -at- outlook.com). Since there’s no documentation yet, I really don’t have a problem answering any questions.

Have you ever tried to use PowerShell (or .NET) to mess with file or folder permissions and wondered what the ‘Synchronize’ right means? It pops up all over the place, like on existing ACEs:

what_does_synchronize_mean_example
And on new ACEs that you create (even if you don’t include it):

synchronize_2

If you try to check permissions using the ACL Editor, you won’t see it anywhere. Here’s the ACE for ‘Users’ from the ‘C:\powershell’ folder shown in the first screenshot above:

synchronize_3_acl_editor

So, what is this mysterious right, and why does PowerShell/.NET insist on showing it everywhere? Let’s start with the definition from MSDN:

The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.

The first time I read that, I didn’t think it sounded all that important. It turns out, though, that it’s critical for working with files and folders.

Before I explain a little bit more about why that right shows up, let’s briefly cover what makes up an access control entry’s access mask. It’s a 32-bit integer, which means that, theoretically, there are 32 different rights that can be controlled (32 bits means 32 different on/off switches). In practice, you don’t get that many rights, though. No matter what type of object you’re working with (file, folder, registry key, printer, service, AD object, etc), those 32-bits are broken down like this:

  • Bits 0-15 are used for object specific rights. These rights differ between object types, e.g., bit 1 for a file means ‘CreateFiles’, for a registry key means ‘SetValue’, and for an AD object means ‘DeleteChild’.
  • Bits 16-23 are used for “Standard access rights”. These rights are shared among the different types of securable objects, e.g., bit 16 corresponds to the right to delete the object, and it means the same thing for files, folders, registry keys, etc. As far as I know, only bits 16-20 in this range do anything.
  • Bit 24 controls access to the SACL.
  • Bits 25-27 are reserved and not currently used.
  • Bits 28-31 are “Generic access rights”. They are a shorthand way of specifying four common access masks: read, write, execute, and all (full control). These bits are translated into a combination of object specific and standard access rights, and the translation differs depending on the type of object the ACE belongs to.

The ‘Synchronize’ right is controlled by bit 20, so it’s one of the standard access rights:

PS> [math]::Log([System.Security.AccessControl.FileSystemRights]::Synchronize, 2)
20

If you manage to remove the right (or if you explicitly deny it), bad things will happen. For folders, you won’t be able to see child items. For files, you won’t be able to view the contents. It turns out some very important Win32 APIs require that right to be granted, at least for file and folder objects. You get a hint of it from this MSDN page:

Note that you cannot use an access-denied ACE to deny only GENERIC_READ or only GENERIC_WRITE access to a file. This is because for file objects, the generic mappings for both GENERIC_READ or GENERIC_WRITE include the SYNCHRONIZE access right. If an ACE denies GENERIC_WRITE access to a trustee, and the trustee requests GENERIC_READ access, the request will fail because the request implicitly includes SYNCHRONIZE access which is implicitly denied by the ACE, and vice versa. Instead of using access-denied ACEs, use access-allowed ACEs to explicitly allow the permitted access rights.

I couldn’t do a good job of translating the actual definition of ‘Synchronize’ earlier, but I think I can translate this paragraph. It’s saying that you can’t create an access denied ACE for just GENERIC_READ or just GENERIC_WRITE as they are defined, because each of those sets of rights include ‘Synchronize’, and you’d effectively be denying both sets of rights. GENERIC_READ (bit 31) and GENERIC_WRITE (bit 30) are two of the four “Generic access rights” mentioned above. When they’re translated/mapped to their object-specific rights, they make up a combination of bits 0-20 of the access mask (object specific and standard rights).

Once translated, GENERIC_READ is very similar to [FileSystemRights]::Read, and GENERIC_WRITE is very similar to [FileSystemRights]::Write. From the same MSDN page, here’s a list of the object specific and standard rights that make up the generic rights (the [FileSystemRights] equivalents are listed in parenthesis):

  • GENERIC_READ
    • FILE_READ_ATTRIBUTES (ReadAttributes)
    • FILE_READ_DATA (ReadData)
    • FILE_READ_EA (ReadExtendedAttributes)
    • STANDARD_RIGHTS_READ (ReadPermissions)
    • SYNCHRONIZE (Synchronize)
  • GENERIC_WRITE
    • FILE_APPEND_DATA (AppendData)
    • FILE_WRITE_ATTRIBUTES (WriteAttributes)
    • FILE_WRITE_DATA (WriteData)
    • FILE_WRITE_EA (WriteExtendedAttributes)
    • STANDARD_RIGHTS_WRITE (ReadPermissions)
    • SYNCHRONIZE (Synchronize)

The [FileSystemRights] enumeration has values for Read and Write that almost match what is defined above. Since PowerShell coerces strings into enumerations, and enumerations will attempt to show you combined flags where possible, let’s take a look at how those rights are seen when they’re cast as a FileSystemRights enumeration:

synchronize_4_generic_to_filesystemrights

Hopefully that makes sense. It’s showing that GENERIC_READ in [FileSystemRights] translates to ‘Read, Synchronize’, which means that GENERIC_READ is not the same as [FileSystemRights]::Read since ‘Read’ doesn’t include ‘Synchronize’. GENERIC_WRITE and [FileSystemRights]::Write are almost the same, except [FileSystemRights]::Write is also missing ‘ReadPermissions’ in addition to ‘Synchronize’.

So, why don’t the generic rights translate to the same numeric values for [FileSystemRights]? It goes back to the warning from the MSDN page above: if you want to deny ‘Read’ or ‘Write’ only, you have to remove the ‘Synchronize’ right first. The ACL editor does this, and it doesn’t give you any control over the ‘Synchronize’ right: if you create a new ACE it will determine whether or not the right is added, and it never shows it to you. The creators of the file/folder access control .NET classes didn’t get that luxury. Each ACE has a numeric access mask, and that access mask needs to be translated with a flags enumeration. If the ‘Synchronize’ bit is set, then the flags enumeration string is going to show it, and vice versa. So, they did the next best thing: they pulled ‘Synchronize’ from the combined ‘Read’ and ‘Write’ rights in the [FileSystemRights] enumeration, and made sure that creating a new allow ACE or audit rule automatically adds the ‘Synchronize’ right, and creating a new deny ACE removes it. If an application wants to hide the ‘Synchronize’ right from the end user, that’s fine, but the underlying .NET object will show it if it’s present.

I hope that makes sense and clears that up. If not, please leave a comment where something needs to be explained a little better, and I’ll try to expand on it some more.

I’ve had a beta version of the PAC module available on Script Center repository for quite a while now. It adds several new features, including the following:

  • Active Directory objects are supported
  • Desired State Configurations (DSC) resources available to automate access control settings (see about_PowerShellAccessControl_DscResources)
  • Supports filenames longer than 260 characters (PSv3 or higher)
  • Shows inheritance source for file, folder, registry key, and Active Directory objects (PSv3 or higher)

The documentation isn’t finished, and there are still a few bugs that I’m aware of that need to be fixed. I’ve been using it in its current form for a while, though, so I feel that it’s pretty stable. Give it a shot and let me know if you have any issues and/or questions (you can post here or on the Q&A section on the repository page.

The biggest problem I have with it is the speed (especially when working with AD objects). I’ve been playing around with moving some of it to C#, and I’ve noticed an amazing speed improvement. At some point in the future, the module will have at least some C#. I may one day make the entire module a compiled module (the source code will always be included).

Other features that will come in the future:

  • Central Access Policies (view/set assigned CAPs for files and folders and view central access rules associated with the CAP)
  • Conditional/Callback ACEs will show conditions (very similar to ACL Editor)
  • File/folder dynamic access control tags will be viewable (and one day settable)
  • Get-EffectiveAccess will show limiting CARs when a CAP is assigned (right now, CAPs should be taken into account, but it won’t show which CAR is limiting access)
  • Get-EffectiveAccess will allow you to add group/device claims

 

There’s a new version of my PowerShellAccessControl module available in the Script Center Repository. It’s got a lot of new stuff in it, so go check it out. I’m going to just show you some random commands to run (and I’ll post a screenshot or two).

The update brings the ability to audit/modify SACLs (object auditing). It also simplifies getting security descriptors for several objects, including services, printers, WMI namespaces, and more (even file/folder and registry entries). PowerShell v2 is also supported (let me know if you find anything that doesn’t work when using v2). It’s still lacking the functions I’ve been working on to actually save the security descriptors back out. Don’t worry, though, because those are coming. I’m also working on other ways for the module to get its hands on more security descriptors.

For now, though, I think it does a pretty good job of letting you audit your ACLs for almost anything you’d want to (and if it doesn’t directly support the object, you can still use New-AdaptedSecurityDescriptor if you know the SDDL or binary form of the SD). You can also use New-AccessControlEntry to create file, folder, and registry ACEs to use with Get-Acl and Set-Acl. That by itself saves several lines of code.

Anyway, go download the module, then run through some of these demo scripts line by line:

Working with services:

# Must be run as admin b/c of GetSecurityDescriptor WMI method

# Get BITs service SD
$SD = Get-Service bits | Get-SecurityDescriptor

# Check out the default formatting
$SD

# And as a list:
$SD | fl

# Show ACEs (access and audit) for services that start with the letter 'B'
Get-Service b* | Get-AccessControlEntry

# Show auditing ACEs for services that start with the letter 'B'
Get-Service b* | Get-AccessControlEntry -AclType Audit

# Get BITs service SD (again)
$SD = Get-Service bits | Get-SecurityDescriptor

# Give users the ability to Start and Stop it:
$SD.AddAccessRule((New-AccessControlEntry -ServiceAccessRights Start, Stop -Principal Users))
# Audit that
$SD.AddAuditRule((New-AccessControlEntry -ServiceAccessRights Start, Stop -Principal Users -AuditSuccess))

# Look to make sure those entries are there:
$SD | fl
#or
$SD.Access
$SD.Audit

# Since there's no Set-SecurityDescriptor yet, do this if you want to save
# SD (you have to remove -WhatIf to make it permanent)
$Win32SD = $SD | ConvertTo-Win32SecurityDescriptor -ValueOnly  # Use -LegacyWmiObject if you're going to use WMI cmdlets
Get-CimInstance Win32_Service -Filter "Name='bits'" | Invoke-CimMethod -MethodName SetSecurityDescriptor -WhatIf -Arguments @{
    Descriptor = $Win32SD
}

Working with other objects:

# Printers
Get-WmiObject Win32_Printer | Get-SecurityDescriptor

# Printer access ACE:
$ACE = New-AccessControlEntry -PrinterRights ManageDocuments -Principal Users

# Logical share:
Get-CimInstance Win32_LogicalShareSecuritySetting | Get-SecurityDescriptor

# WSMan:
dir wsman: -Recurse | ? { $_.Name -eq "SDDL" } | Get-SecurityDescriptor

# Folder:
get-item c:\windows | Get-SecurityDescriptor
dir c:\windows -Directory | Get-AccessControlEntry -AceNotInherited
dir c:\windows -Directory | Get-AccessControlEntry -IdentityReference Administrators

# WMI namespace:
$SD = Get-CimInstance __SystemSecurity -Namespace root/cimv2 | Get-SecurityDescriptor

# Add an access ACE that also applies to all child namespaces:
$SD.AddAccessRule((New-AccessControlEntry -WmiNamespaceRights RemoteEnable -Principal Users -AppliesTo Object, ChildContainers))

# View the modified ACL:
$SD.Access
 
# Get new SDDL:
$SD.Sddl
 
# Get new binary form:
$SD.GetSecurityDescriptorBinaryForm()

# Remember, WMI namespace SD hasn't been modified for real, just in in-memory instance of SD

Audit all WMI namespace rights:

function Get-ChildNamespace {

    param(
        [string] $Namespace = "root",
        [int] $Level = 1
    )

    # Decrement level (if argument wasn't supplied, you'll only get
    # the direct chidren)
    $Level--

    Get-WmiObject __Namespace -Namespace $Namespace | select -exp name | ForEach-Object {

        [PsCustomObject] @{
            FullName = "$Namespace\$_"
            Name = $_
            Namespace = $Namespace
        }

        # Negative numbers mean recurse forever
        if ($Level) {
            & $MyInvocation.MyCommand -Namespace "$Namespace\$_" -RecurseLevel $Level
        }
    }
}

# Store SDs for all namesapces in $WmiNsSD (gwmi)
$WmiSDs = Get-ChildNamespace -Level -1 | 
    select -exp fullname | 
    % {"root"}{ $_ } | 
    sort | 
    % { Get-WmiObject -EnableAllPrivileges __SystemSecurity -Namespace $_ } | 
    Get-SecurityDescriptor

# Just show with default formatting:
$WmiSDs

# Or show with ACEs expanded as their own objects
$WmiSDs | Get-AccessControlEntry


# Only show ACEs that aren't inherited:
$WmiSDs | Get-AccessControlEntry -AceNotInherited

And here is a screenshot showing the default formatting after calling ‘Get-Service b* | Get-AccessControlEntry’:

Get-Service b* | Get-AccessControlEntry

Get-Service b* | Get-AccessControlEntry

And one after calling ‘Get-Service bits | Get-SecurityDescriptor’

Get-Service | Get-SecurityDescriptor

Get-Service | Get-SecurityDescriptor

There are a lot of examples in the comment based help for the functions, too. If you have any issues/suggestions, please let me know.