none
Regex For SDDL RRS feed

  • Question

  • Hi guys,

    I have a task to edit an SDDL string.  The following is an example:

    D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)**S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)


    The ** is where I need to insert an ACE, not part of the SDDL string.

    I have the following POSH code to test finding the insertion position:

    $sddl = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    $pattern = '^D:(\([AD];;([A-Z][A-Z])+;;;\w+\))+'
    $sddl -cmatch $pattern
    $Matches

    And here is the output:

    Name                           Value                                                                                                                                                                  
    ----                           -----                                                                                                                                                                  
    2                              RC                                                                                                                                                                     
    1                              (A;;CCLCSWRPWPDTLOCRRC;;;PU)                                                                                                                                           
    0                              D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)      

    My question is, how can I adjust the regex pattern to accurately find out the insertion position (i.e. at the end of the D: part, and right before S: (if exist)).

    Thanks ahead




    Tuesday, December 10, 2013 5:51 PM

Answers

  • I finally came up with this:

    $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-21-3715011275-124393379-1477327865-1032')
    
    #In reality, the SDDL will be extracted from SC or WMI
    $sddl ='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    $accessMask = 0x01FD #General Read + General Execute, see MS Article "Service Security and Access Rights"
    
    $rawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)
    $ace = New-Object System.Security.AccessControl.CommonAce([System.Security.AccessControl.AceFlags]::None,[System.Security.AccessControl.AceQualifier]::AccessAllowed,$accessMask,$sid,$false,$null)
    
    if ($rawSD.DiscretionaryAcl.GetEnumerator() -notcontains $ace)
    {
        $rawSD.DiscretionaryAcl.InsertAce($rawSD.DiscretionaryAcl.Count,$ace)
    }
    
    $newSDDL = $rawSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    $newSDDL
    
    #Write the SDDL back to the service, using SC, WMI, etc.



    this method should be foolproof, you can't mess up the SDDL with this.

    Thanks everyone for your input and great directions.
    Thursday, December 12, 2013 6:51 AM

All replies

  • As much as I love regular expressions, I think I'd stay away from trying to manipulate SDDL directly.  I'd be almost sure of missing some legal character that my expression would ignore.

    Instead, you could just use the .NET Framework security classes to do the work for you.  For example:

    $sddl = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    
    $security = New-Object System.Security.AccessControl.DirectorySecurity
    $security.SetSecurityDescriptorSddlForm($sddl)
    
    # Create a FileSystemAccessRule and add it with $security.AddAccessRule
    
    $newSddl = $security.GetSecurityDescriptorSddlForm('Access,Audit')
    
    

    Tuesday, December 10, 2013 6:00 PM
  • Thanks David.  Do you know which class under System.Security.AccessControl is suitable for handling ACLs for Services?
    Tuesday, December 10, 2013 8:51 PM
  • Hmm, this is annoying.  There doesn't seem to be one.  A web search turned up a few instances of people writing their own class (subclassing from NativeObjectSecurity) in C#, but for some reason the .NET Framework doesn't include that out of the box.
    Tuesday, December 10, 2013 10:55 PM
  • I know next to nothing about SDDLs, so my suggestion might be totally off:

    $ace="myACE"
    $sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    $match=[regex]::Match($sddl,'S:\(')
    if ($match.Success){
    	$sddl.Substring(0,$match.Index) + $ace + $sddl.Substring($match.Index)
    }
    

    Wednesday, December 11, 2013 12:32 AM
  • That's actually pretty reasonable, if you assume that the original SDDL is in the correct format.  I was thinking of something similar, doing a general pattern like this:

    $ace="myACE"
    $sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    
    if ($sddl -match '^(.*?D:(?:\([^\)]+\))+)(.*)$')
    {
        "$($matches[1])$ace$($matches[2])"
    }

    It's a little bit less general, though still not even close to a validation of a legal SDDL string.  It just looks for the pattern of "D:(Whatever1)..(WhateverN)"
    • Edited by David Wyatt Wednesday, December 11, 2013 1:14 AM
    Wednesday, December 11, 2013 1:13 AM
  • Thanks Dirk.  The only flaw there is there isn't necessarily always an SACL entry.  More than often the whole string will just start with D: and then followed by a series of ACEs in round brackets, that's it.

    But you've confirmed my first approach is in the right direction - to use Regex.
    Wednesday, December 11, 2013 9:22 PM
  • Try this module

    Then you can feed the SDDL to the New-AdaptedSecurityDescriptor function, add an ACE, then get the resulting SDDL:

    $SD = New-AdaptedSecurityDescriptor -Sddl "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)" -AccessMaskEnumeration ([PowerShellAccessControl.ServiceAccessRights])
    
    # Give Authenticated Users permission to start and stop the service:
    $ACE = New-AccessControlEntry -Principal "Authenticated Users" -AccessMask ([PowerShellAccessControl.ServiceAccessRights] "Start,Stop")
    $SD.AddAccessRule($ACE)
    $SD.Sddl

    You can also use it to get the security descriptor from other services:

    # NOTE: This must be run from an elevated prompt b/c of WMI method call to get SD
    Get-CimInstance Win32_Service -Filter "Name='BITS'" | 
      Get-Win32SecurityDescriptor -Sddl | 
      New-AdaptedSecurityDescriptor -AccessMaskEnumeration ([PowerShellAccessControl.ServiceAccessRights])


    • Edited by Rohn Edwards Wednesday, December 11, 2013 10:01 PM
    Wednesday, December 11, 2013 10:01 PM
  • It works just fine.  JUst add the SDDL to an ACL and edit the ACL using the edit methods.

    Try it:

    $sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    
    $acl=New-Object System.Security.AccessControl.FileSecurity
    $acl.SetSecurityDescriptorSddlForm($sddl)
    $acl.Access
    


    ¯\_(ツ)_/¯

    Wednesday, December 11, 2013 11:26 PM
  • Thanks Dirk.  The only flaw there is there isn't necessarily always an SACL entry.  More than often the whole string will just start with D: and then followed by a series of ACEs in round brackets, that's it.

    But you've confirmed my first approach is in the right direction - to use Regex.
    Have you tried the regex pattern I posted? It was intended to be flexible that way; it looks for zero or more characters leading up to a pattern of D:(stuff)...(stuffN) , followed by any number of characters, and injects your string after the last set of parentheses following "D:".  (Unless I screwed it up and it doesn't work at all... if so, let me know.)
    Thursday, December 12, 2013 1:30 AM
  • Thanks Dirk.  The only flaw there is there isn't necessarily always an SACL entry.  More than often the whole string will just start with D: and then followed by a series of ACEs in round brackets, that's it.

    But you've confirmed my first approach is in the right direction - to use Regex.

    Have you tried the regex pattern I posted? It was intended to be flexible that way; it looks for zero or more characters leading up to a pattern of D:(stuff)...(stuffN) , followed by any number of characters, and injects your string after the last set of parentheses following "D:".  (Unless I screwed it up and it doesn't work at all... if so, let me know.)
    Thank you very much David.  I was actually testing your code and then got side tracked to other stuff.  I need some body doubles to do the work for me lol.

    Yes your code works perfectly fine, in the sense that it uses straight string manipulation.  I have actually been thinking about this and since I'm mucking with services there is no room for error, I'd better go for a safer way such as using those classes under S.S.AccessControl.  It is just a matter of finding the right classes and methods that will do the job without giving me a chance to screw up the string.
    Thursday, December 12, 2013 6:39 AM
  • It works just fine.  JUst add the SDDL to an ACL and edit the ACL using the edit methods.

    Try it:

    $sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    
    $acl=New-Object System.Security.AccessControl.FileSecurity
    $acl.SetSecurityDescriptorSddlForm($sddl)
    $acl.Access


    ¯\_(ツ)_/¯

    That may work fine but I need to be 100% sure it will work for *Services*.  There is no room for error.

    Thanks for the answer though, it gave me some ideas.
    Thursday, December 12, 2013 6:42 AM
  • I finally came up with this:

    $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-21-3715011275-124393379-1477327865-1032')
    
    #In reality, the SDDL will be extracted from SC or WMI
    $sddl ='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    $accessMask = 0x01FD #General Read + General Execute, see MS Article "Service Security and Access Rights"
    
    $rawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)
    $ace = New-Object System.Security.AccessControl.CommonAce([System.Security.AccessControl.AceFlags]::None,[System.Security.AccessControl.AceQualifier]::AccessAllowed,$accessMask,$sid,$false,$null)
    
    if ($rawSD.DiscretionaryAcl.GetEnumerator() -notcontains $ace)
    {
        $rawSD.DiscretionaryAcl.InsertAce($rawSD.DiscretionaryAcl.Count,$ace)
    }
    
    $newSDDL = $rawSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    $newSDDL
    
    #Write the SDDL back to the service, using SC, WMI, etc.



    this method should be foolproof, you can't mess up the SDDL with this.

    Thanks everyone for your input and great directions.
    Thursday, December 12, 2013 6:51 AM
  • Try this module

    Then you can feed the SDDL to the New-AdaptedSecurityDescriptor function, add an ACE, then get the resulting SDDL:

    $SD = New-AdaptedSecurityDescriptor -Sddl "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)" -AccessMaskEnumeration ([PowerShellAccessControl.ServiceAccessRights])
    
    # Give Authenticated Users permission to start and stop the service:
    $ACE = New-AccessControlEntry -Principal "Authenticated Users" -AccessMask ([PowerShellAccessControl.ServiceAccessRights] "Start,Stop")
    $SD.AddAccessRule($ACE)
    $SD.Sddl

    You can also use it to get the security descriptor from other services:

    # NOTE: This must be run from an elevated prompt b/c of WMI method call to get SD
    Get-CimInstance Win32_Service -Filter "Name='BITS'" | 
      Get-Win32SecurityDescriptor -Sddl | 
      New-AdaptedSecurityDescriptor -AccessMaskEnumeration ([PowerShellAccessControl.ServiceAccessRights])


    Thanks Rohn, I'll definitely give it a try once we've upgraded to PSv3.
    Thursday, December 12, 2013 6:52 AM
  • It works just fine.  JUst add the SDDL to an ACL and edit the ACL using the edit methods.

    Try it:

    $sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    
    $acl=New-Object System.Security.AccessControl.FileSecurity
    $acl.SetSecurityDescriptorSddlForm($sddl)
    $acl.Access


    ¯\_(ツ)_/¯

    That may work fine but I need to be 100% sure it will work for *Services*.  There is no room for error.

    Thanks for the answer though, it gave me some ideas.

    That is what we have used for a few years.  The system will decode it correctly.  THe RegeX method can have issues do to resorting of ACEs. 

    A service DACL is an object DACL.  It is just stored in the registry as a binary.  You cannot edit the registry directly because some encoding is added.  The SC Manager has an API that knows how to write the registry.  WMI can also do this.

    The Service DACL is sa subset of the file security.  Load your SDDL into a file ACL and analyze it to see what is happening.  You will have an ACE for each Truestee.  The ACE will have a binary security mask and a Text translation.

    Look up how it is done in C/C++.  You will  see that they use the same structures.  The SDK has the Service specific strings.


    ¯\_(ツ)_/¯

    Thursday, December 12, 2013 7:26 AM
  • I finally came up with this:

    $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-21-3715011275-124393379-1477327865-1032')
    
    #In reality, the SDDL will be extracted from SC or WMI
    $sddl ='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
    $accessMask = 0x01FD #General Read + General Execute, see MS Article "Service Security and Access Rights"
    
    $rawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)
    $ace = New-Object System.Security.AccessControl.CommonAce([System.Security.AccessControl.AceFlags]::None,[System.Security.AccessControl.AceQualifier]::AccessAllowed,$accessMask,$sid,$false,$null)
    
    if ($rawSD.DiscretionaryAcl.GetEnumerator() -notcontains $ace)
    {
        $rawSD.DiscretionaryAcl.InsertAce($rawSD.DiscretionaryAcl.Count,$ace)
    }
    
    $newSDDL = $rawSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    $newSDDL
    
    #Write the SDDL back to the service, using SC, WMI, etc.



    this method should be foolproof, you can't mess up the SDDL with this.

    Thanks everyone for your input and great directions.

    This method is very workable.  You can set up all of the accessmask values by creating an enum from the SDK or just edit with raw numbers if you only have a couple.

    Using this method you will find to be much safer than a RegEx and really much easier to manage in code.


    ¯\_(ツ)_/¯

    Thursday, December 12, 2013 7:57 AM