Posts RSS Comments RSS 117 Posts and 170 Comments till now

userAccountControl and “User cannot change password”

Someone asked me a question about setting the “User cannot change password” check box in ADUC. They were creating the user account and setting PASSWD_CANT_CHANGE along with other settings (see my post about HERE about userAccountControl and values) and they couldn’t figure out why the check box wasn’t being applied.

I thought about this, and my first impression was they had the wrong bit value. So, I posted the “correct” one. They came back and said “That didn’t work.”

Hmmm, that’s curious. I turned to Dean and asked him if I was missing something obvious (I had a nagging feeling I had been here before) and he informed me that it has to be set with an “Extended Right” (control access right) via an ACE.

DOH! Now the whole scenario I had THAT feeling about came back to me. I recall having this discussion with someone and providing them a Script that would set the ACE. I searched for the script and I couldn’t find it. This happens to me a lot so I decided a while ago… when I run across this again, BLOG IT!

Technical Info:

In the past, permissions on the ‘userAccountControl’ attribute could be edited oftentimes making the effective password policy moot, i.e. you could end up with accounts that don’t comply with the domain’s password policy.

In Windows 2000, you can’t easily prevent this except by using third party front-end/provisioning tools to manage user objects. In Windows 2003 and later, you can use three newly-added extended rights (Control Access Rights) to prevent these bits from being edited even when the caller has permission to do so. The three (new) ‘Extended Rights’ are -

• Update password not required bit [controls 'password not required' and maps to ACE in footnote below]
• Enable per user reversible encryption [controls whether password is stored reversibly encrypted or not]
• Unexpire password [controls 'password never expires']

Each of these extended rights MUST be configured on the domain head and scoped as “This Object only” with ALLOW or DENY for the security principals you designate. By default, ‘Authenticated Users’ is granted an ALLOW ACE for each of the three extended rights. This doesn’t mean any old authenticated user can alter the password related bits in the ‘userAccountControl’ attribute; they still require the permission to modify ‘userAccountControl’.

USAGE SCENARIO - create a single group representing ALL three extended rights (or perhaps ONE group for EACH extended right). Then ACL the group(s) accordingly on the domain head with a DENY ACE. Finally, place the account-administrator users and groups that have management permissions to user objects (i.e. they have write permissions to the ‘userAccountControl’ property) in the group(s) you just created thereby preventing those account-administrators from altering the password related bits on the ‘userAccountControl’ attribute resulting in an enforced password policy.

IMPORTANT NOTE [Observed Behavior] - When viewing or changing a user’s ability to change their own password (User Cannot Change Password) through the GUI, it no longer appears to touch ‘userAccountControl’s bit 0×40 (64) — rather, it simply grant’s the ‘SELF’ security principal ‘ALLOW’ or ‘DENY’ to ‘Change Password’ — this can be easily verified by viewing the DACL.

Links:
Modifying User Cannot Change Password (LDAP Provider)

So… Here it is (Set-UserCannotChangePassword.ps1)
Parameters
-User: The sAMAccountName of the User
-CheckBox: If passed it checks the box
-Default: Remove Check box.

  1. Param($User = $(throw ‘$User is Required’,[switch]$CheckBox)
  2. Write-Host
  3.  
  4. $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"","(&(objectcategory=User)(sAMAccountName=$user))")
  5. $MyUser = $Searcher.FindOne().GetDirectoryEntry()
  6.  
  7. if(!$?){" !! Failed to Get User !!";Return}
  8.  
  9. if($CheckBox)
  10. {
  11.     Write-Host " - Checking Box for User [$($MyUser.distinguishedName)]"
  12.     $self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10′
  13.     $ExtendedRight = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight
  14.     $deny = [System.Security.AccessControl.AccessControlType]::Deny
  15.     $selfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule($self,$ExtendedRight,$deny,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
  16.     $MyUser.psbase.get_ObjectSecurity().AddAccessRule($selfDeny)
  17.     $MyUser.psbase.CommitChanges()
  18. }
  19. else
  20. {
  21.     Write-Host " - Removing Check Box for User [$($MyUser.distinguishedName)]"
  22.     $ACL = $MyUser.psbase.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount])
  23.     $ACEs = $ACL | ?{($_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’) -and ($_.AccessControlType -eq ‘Deny’)}
  24.     foreach($ACE in $ACEs){if($ACE){[void]$MyUser.psbase.get_ObjectSecurity().RemoveAccessRule($ACE)}}
  25.     $MyUser.psbase.CommitChanges()
  26. }
  27.  
  28. Write-Host

Get/Set-ADACL (ACL and SDDLs for Active Directory!)

A friend had a need to get/set Active Directory ACLs. So I wrote these.

They will use [System.DirectoryServices.ActiveDirectoryAccessRule] objects or SDDLs strings.

Note: I put the .NET classes and MS Spec for SDDLs at the bottom. Dont miss it!

Get-ADACL.ps1

  1. # Get-ADACL.ps1
  2. Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
  3. function HelpMe{
  4.     Write-Host
  5.     Write-Host " Get-ADACL.ps1:" -fore Green
  6.     Write-Host "   Gets ACL object or SDDL for AD Object"
  7.     Write-Host
  8.     Write-Host " Parameters:" -fore Green
  9.     Write-Host "   -DNPath                : Parameter: DN of Object"
  10.     Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
  11.     Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
  12.     Write-Host "   -Help                  : [SWITCH]:  Displays This"
  13.     Write-Host
  14.     Write-Host " Examples:" -fore Green
  15.     Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
  16.     Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
  17.     Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
  18.     Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
  19.     Write-Host
  20. }
  21.  
  22. if(!$DNPath -or $help){HelpMe;return}
  23.  
  24. Write-Host
  25. if($verbose){$verbosepreference="continue"}
  26.  
  27. Write-Verbose " + Processing Object [$DNPath]"
  28. $DE = [ADSI]"LDAP://$DNPath"
  29.  
  30. Write-Verbose "   - Getting ACL"
  31. $acl = $DE.psbase.ObjectSecurity
  32. if($SDDL)
  33. {
  34.     Write-Verbose "   - Returning SDDL"
  35.     $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
  36. }
  37. else
  38. {
  39.     Write-Verbose "   - Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
  40.     $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
  41. }

Set-ADACL.ps1

  1. # Set-ADACL.ps1
  2. Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
  3. function HelpMe{
  4.     Write-Host
  5.     Write-Host " Set-ADACL.ps1:" -fore Green
  6.     Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
  7.     Write-Host
  8.     Write-Host " Parameters:" -fore Green
  9.     Write-Host "   -DNPath                : Parameter: DN of Object"
  10.     Write-Host "   -ACL                   : Parameter: ACL Object"
  11.     Write-Host "   -sddl                  : Parameter: SDDL String"
  12.     Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
  13.     Write-Host "   -Help                  : [SWITCH]:  Displays This"
  14.     Write-Host
  15.     Write-Host " Examples:" -fore Green
  16.     Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
  17.     Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
  18.     Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
  19.     Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
  20.     Write-Host
  21. }
  22.  
  23. if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}
  24.  
  25. Write-Host
  26. if($verbose){$verbosepreference="continue"}
  27. Write-Verbose " + Processing Object [$DNPath]"
  28.  
  29. $DE = [ADSI]"LDAP://$DNPath"
  30. if($sddl)
  31. {
  32.     Write-Verbose "   - Setting ACL using SDDL [$sddl]"
  33.     $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
  34. }
  35. else
  36. {
  37.     foreach($ace in $acl)
  38.     {
  39.         Write-Verbose "   - Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
  40.         $DE.psbase.ObjectSecurity.SetAccessRule($ace)
  41.     }
  42. }
  43. $DE.psbase.commitchanges()
  44. Write-Host

More Info
I used the following .NET Classes
System.DirectoryServices.DirectoryEntry
http://msdn2.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx
System.DirectoryServices.ActiveDirectoryAccessRule
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectoryaccessrule.aspx
System.DirectoryServices.ActiveDirectorySecurity
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurity.aspx
System.Security.AccessControl.AccessControlSections
http://msdn2.microsoft.com/en-us/library/system.security.accesscontrol.accesscontrolsections(vs.80).aspx

SDDL Info
MS: http://msdn2.microsoft.com/en-us/library/aa379567.aspx

Converting Secure String

I recently heard a question about ConvertTO-SecureString and ConvertFrom-SecureString.

These CMDLets may be a little confusing so let’s talk a little about them.

Before you read the rest, it is key to understand what a Secure String actually is.

A Secure String is simple text that is encrypted in memory. This lets you store passwords or other secure data in memory without having to concern yourself with someone snooping your session or dumping your memory contents to get your data.

More Info Here
http://msdn2.microsoft.com/en-us/library/system.security.securestring.aspx

ConvertTo-SecureString: http://technet.microsoft.com/en-us/library/bb978707.aspx
I think this where it gets a little confusing for people. ConvertTo is meant to take an encrypted string and store it as a Secure String. Specificially for the output from ConvertFrom-SecureString. It will allow you use -asPlainText w/ -Force if you just want to convert a piece of text to Secure String.

ConvertFrom-SecureString: http://technet.microsoft.com/en-us/library/bb978629.aspx
This is used to convert a Secure String to text. It is import to note…. this is NOT the original text but the string representation of the encryption. This is a great tool for exporting the Secure String to a file in an encrypted form.

One other note before I show the code. By default (unless you provide a key) it uses Windows Data Protection API (DPAPI). This is VERY important. This process it is fairly secure, but can only decrypted by you on that specific machine. On the flip side… using a Key is not near as secure and some would argue security by obscurity.

Now… lets look at what we have… nothing magic here. We have three functions Export-EncryptedText. Import-EncryptedText, and Get-EncryptedText.
Export-EncryptedText: This gets a Secure String and exports to a file
Import-EncryptedText: This imports a Secure String Text from a file and returns a Secure String
Get-EncryptedText: This converts a Secure String into the orginal Text

We use “Read-Host -AsSecureString” to create convert our text to a secure string.

  1. function Export-EncryptedText{
  2.     param($text,$file,$key)
  3.     if($key){ConvertFrom-SecureString -SecureString $text -key $key | out-file $file}
  4.     else{ConvertFrom-SecureString -SecureString $text | out-file $file}
  5. }
  6. function Import-EncryptedText{
  7.     Param($file,$key)
  8.     $textFromFile = Get-Content $file
  9.     if($key){ConvertTO-SecureString $TextFromFile -key $key}
  10.     else{ConvertTO-SecureString $TextFromFile}
  11. }
  12. function Get-EncryptedText($text) {
  13.     $Ptr = [System.Runtime.InteropServices.Marshal ]::SecureStringToCoTaskMemUnicode($text)
  14.     $result = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
  15.     [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
  16.     $result
  17. }
  18.  
  19. # Example Use
  20. $dataToStore = Read-Host -AsSecureString
  21. "My String To Encrypt Here"
  22.  
  23. # Key… needs to be a 16, 24, or 32 byte array
  24. $key = (200..231)
  25.  
  26. # Use this to put the password in a file
  27. Export-EncryptedText -text $dataToStore -file c:\data\testfile.secure -key $key
  28.  
  29. # To get the password back you do this
  30. $myText = Import-EncryptedText c:\data\testfile.secure -key $key
  31.  
  32. # To see the text use Get-SecurePass
  33. Get-EncryptedText $myText

Sync Folder Script.

I initially wrote this for a guy on the NG, but I decided to use at work to sync some application folders I used to store tools and scripts and such. Please Try it out and tell me what you think.

Basic Flow
- Checks for Folders that Source has the Destination Does NOT and Creates.
- Checks for Folders that Destination has the Source Does NOT and Creates.
- Checks for files that Source has the Destination Does NOT and Copies.
- If the file is found in both, the MD5 of both files are checked… Last Write Wins
- Checks for Folders that Destination has the Source Does NOT
- It does NOT Delete anything yet… I am considering using a mirror switch

  1. Param($Source,$Destination)
  2. function Get-FileMD5 {
  3.    Param([string]$file)
  4.    $mode = [System.IO.FileMode]("open")
  5.    $access = [System.IO.FileAccess]("Read")
  6.    $md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
  7.    $fs = New-Object System.IO.FileStream($file,$mode,$access)
  8.    $Hash = $md5.ComputeHash($fs)
  9.    $fs.Close()
  10.    [string]$Hash = $Hash
  11.    Return $Hash
  12. }
  13. function Copy-LatestFile{
  14.     Param($File1,$File2,[switch]$whatif)
  15.     $File1Date = get-Item $File1 | foreach-Object{$_.LastWriteTimeUTC}
  16.     $File2Date = get-Item $File2 | foreach-Object{$_.LastWriteTimeUTC}
  17.     if($File1Date -gt $File2Date)
  18.     {
  19.         Write-Host "$File1 is Newer… Copying…"
  20.         if($whatif){Copy-Item -path $File1 -dest $File2 -force -whatif}
  21.         else{Copy-Item -path $File1 -dest $File2 -force}
  22.     }
  23.     else
  24.     {
  25.         Write-Host "$File2 is Newer… Copying…"
  26.         if($whatif){Copy-Item -path $File2 -dest $File1 -force -whatif}
  27.         else{Copy-Item -path $File2 -dest $File1 -force}
  28.     }
  29.     Write-Host
  30. }
  31.  
  32. if(!(test-Path $Destination))
  33. {
  34.     New-Item $Destination -type Directory -force | out-Null
  35. }
  36.  
  37. # Getting Files/Folders from Source and Destination
  38. $SrcEntries = Get-ChildItem $Source -Recurse
  39. $DesEntries = Get-ChildItem $Destination -Recurse
  40.  
  41. # Parsing the folders and Files from Collections
  42. $Srcfolders = $SrcEntries | Where-Object{$_.PSIsContainer}
  43. $SrcFiles = $SrcEntries | Where-Object{!$_.PSIsContainer}
  44. $Desfolders = $DesEntries | Where-Object{$_.PSIsContainer}
  45. $DesFiles = $DesEntries | Where-Object{!$_.PSIsContainer}
  46.  
  47. # Checking for Folders that are in Source, but not in Destination
  48. foreach($folder in $Srcfolders)
  49. {
  50.     $SrcFolderPath = $source -replace "\\","\\" -replace "\:","\:"
  51.     $DesFolder = $folder.Fullname -replace $SrcFolderPath,$Destination
  52.     if($DesFolder -ne ""){
  53.         if(!(test-path $DesFolder))
  54.         {
  55.             Write-Host "Folder $DesFolder Missing. Creating it!"
  56.             new-Item $DesFolder -type Directory | out-Null
  57.         }
  58.     }
  59. }
  60.  
  61. # Checking for Folders that are in Destinatino, but not in Source
  62. foreach($folder in $Desfolders)
  63. {
  64.     $DesFilePath = $Destination -replace "\\","\\" -replace "\:","\:"
  65.     $SrcFolder = $folder.Fullname -replace $DesFilePath,$Source
  66.     if($srcFolder -ne "")
  67.     {
  68.         if(!(test-path $SrcFolder))
  69.         {
  70.             Write-Host "Folder $SrcFolder Missing. Creating it!"
  71.             new-Item $SrcFolder -type Directory | out-Null
  72.         }
  73.     }
  74. }
  75.  
  76. # Checking for Files that are in the Source, but not in Destination
  77. foreach($entry in $SrcFiles)
  78. {
  79.     $SrcFullname = $entry.fullname
  80.     $SrcName = $entry.Name
  81.     $SrcFilePath = $Source -replace "\\","\\" -replace "\:","\:"
  82.     $DesFile = $SrcFullname -replace $SrcFilePath,$Destination
  83.     if(test-Path $Desfile)
  84.     {
  85.         $SrcMD5 = Get-FileMD5 $SrcFullname
  86.         $DesMD5 = Get-FileMD5 $DesFile
  87.         If($srcMD5 -ne $desMD5)
  88.         {
  89.             Write-Host "The Files MD5’s are Different… Checking Write Dates"
  90.             Write-Host $SrcMD5
  91.             Write-Host $DesMD5
  92.             Copy-LatestFile $SrcFullname $DesFile
  93.         }
  94.     }
  95.     else
  96.     {
  97.         Write-Host "$Desfile Missing… Copying from $SrcFullname"
  98.         copy-Item -path $SrcFullName -dest $DesFile -force
  99.     }
  100. }
  101.  
  102. # Checking for Files that are in the Destinatino, but not in Source
  103. foreach($entry in $DesFiles)
  104. {
  105.     $DesFullname = $entry.fullname
  106.     $DesName = $entry.Name
  107.     $DesFilePath = $Destination -replace "\\","\\" -replace "\:","\:"
  108.     $SrcFile = $DesFullname -replace $DesFilePath,$Source
  109.     if($SrcFile -ne "")
  110.     {
  111.         if(!(test-Path $SrcFile))
  112.         {
  113.             Write-Host "$SrcFile Missing… Copying from $DesFullname"
  114.             copy-Item -path $DesFullname -dest $SrcFile -force
  115.         }
  116.     }
  117. }
  118.  
  119. Write-Host