Local Administrator Password Solution (LAPS) has been around for a while and last year it became an official supported tool by Microsoft (don’t know if my tweaks are though) and there is a lot of articles about implementing LAPS, which is a no brainer and it works great.
Since there are so many articles about it, I would like to share my tweaks. Consider this article more about having fun and exploring the possibilities.
If your new to it and want to read more about LAPS, you can go here: Microsoft Security Advisory 3062591 and here: Security Thoughts: Microsoft Local Administrator Password Solution (LAPS, KB3062591)
And a mini threat model by Jessica Payne: Local Administrator Password Solution (LAPS) Implementation Hints and Security Nerd Commentary (including mini threat model)
Here’s my view on it.
Implementing LAPS will need a schema update adding two new attributes to the Computer class:
ms-Mcs-AdmPwd – Where the password is stored in clear text.
ms-Mcs-AdmPwdExpirationTime – Stores the time to reset the password
This is easily done by using the powershell module that comes with the LAPS install package. Run the command Import-module AdmPwd.PS and Update-AdmPwdADSchema and it’s all done.
Here I take a different approach. I decided to create my own LDF file to extend the schema with this two attributes because I feel that a few things are missing.
dn: CN=ms-Mcs-AdmPwd,CN=Schema,CN=Configuration,DC=X
changetype: ntdsSchemaAdd
objectClass: attributeSchema
ldapDisplayName: ms-Mcs-AdmPwd
adminDisplayName: ms-Mcs-AdmPwd
adminDescription: LAPS Password Attribute Confidential data
attributeId: 1.2.840.113556.1.8000.2554.50051.45980.28112.18903.35903.6685103.1224907.2.1
attributeSyntax: 2.5.5.5
omSyntax: 19
isSingleValued: TRUE
systemOnly: FALSE
searchFlags: 904
isMemberOfPartialAttributeSet: FALSE
#Add SchemaIDGUID {d8af3522-7870-4e55-bbfe-528dd238a521}
schemaIdGuid:: IjWv2HB4VU67/lKN0jilIQ==
showInAdvancedViewOnly: FALSE
dn: CN=ms-Mcs-AdmPwdExpirationTime,CN=Schema,CN=Configuration,DC=X
changetype: ntdsSchemaAdd
objectClass: attributeSchema
ldapDisplayName: ms-Mcs-AdmPwdExpirationTime
adminDisplayName: ms-Mcs-AdmPwdExpirationTime
adminDescription: LAPS Password Expiration Time Attribtue
attributeId: 1.2.840.113556.1.8000.2554.50051.45980.28112.18903.35903.6685103.1224907.2.2
attributeSyntax: 2.5.5.16
omSyntax: 65
isSingleValued: TRUE
systemOnly: FALSE
searchFlags: 0
isMemberOfPartialAttributeSet: FALSE
#Add SchemaIDGUID {6a51959d-3042-482d-a784-5ad74718fe44}
schemaIdGuid:: nZVRakIwLUinhFrXRxj+RA==
showInAdvancedViewOnly: FALSE
The thing that differ is the adminDescription, I think it’s quite nice to have a description. The other part is that when implementing LAPS the schemaIDGUID is automatically generated by the Domain Controller and will vary in every Forest.
This attribute specifies a unique GUID that identifies this attribute, and is used in security descriptors. It is required on an attributeSchema object. If omitted during Add, the server will auto-generate a random GUID
Link: https://msdn.microsoft.com/en-us/library/cc220843.aspx
It’s not a requirement to specify a schemaIDGUID, but it is a good practice to do it when extending schemas. I think it’s much nicer to have a fixed value to keep consistency and it feels cleaner, so I generated my own that I use.
Now I know the schemaIDGUIDS will always be: {d8af3522-7870-4e55-bbfe-528dd238a521} and {6a51959d-3042-482d-a784-5ad74718fe44}.
This will make delegation easier, but it can be solved other way like building a GUID map in powershell scripts where you map it to e.g. LDAP-Display-Name and there are many examples if you google it.
$guidmap = @{}
Get-ADObject -SearchBase ($RootDSE.SchemaNamingContext) -LDAPFilter `
“(schemaidguid=*)” -Properties lDAPDisplayName,schemaIDGUID |
% {$guidmap[$_.lDAPDisplayName]=[System.GUID]$_.schemaIDGUID}
Another thing I modify is the defaultSecurityDescriptor for the Computer class and we can take advantage of the schemaIDGUID immediately.
dn: CN=Computer,CN=Schema,CN=Configuration,DC=ad,DC=secid,DC=se
changetype: ntdsSchemaModify
replace: defaultSecurityDescriptor
defaultSecurityDescriptor: D:(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;AO)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;;RPLCLORCSDDT;;;CO)(OA;;WP;4c164200-20c0-11d0-a768-00aa006e0529;;CO)(A;;RPLCLORC;;;AU)(OA;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;WD)(A;;CCDC;;;PS)(OA;;CCDC;bf967aa8-0de6-11d0-a285-00aa003049e2;;PO)(OA;;RPWP;bf967a7f-0de6-11d0-a285-00aa003049e2;;CA)(OA;;SW;f3a64788-5306-11d1-a9c5-0000f80367c1;;PS)(OA;;RPWP;77B5B886-944A-11d1-AEBD-0000F80367C1;;PS)(OA;;SW;72e39547-7b18-11d1-adef-00c04fd8d5cd;;PS)(OA;;SW;72e39547-7b18-11d1-adef-00c04fd8d5cd;;CO)(OA;;SW;f3a64788-5306-11d1-a9c5-0000f80367c1;;CO)(OA;;WP;3e0abfd0-126a-11d0-a060-00aa006c33ed;bf967a86-0de6-11d0-a285-00aa003049e2;CO)(OA;;WP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967a86-0de6-11d0-a285-00aa003049e2;CO)(OA;;WP;bf967950-0de6-11d0-a285-00aa003049e2;bf967a86-0de6-11d0-a285-00aa003049e2;CO)(OA;;WP;bf967953-0de6-11d0-a285-00aa003049e2;bf967a86-0de6-11d0-a285-00aa003049e2;CO)(OA;;RP;46a9b11d-60ae-405a-b7e8-ff8a58d456d2;;S-1-5-32-560)(OA;;WP;d8af3522-7870-4e55-bbfe-528dd238a521;;PS)(OA;;RPWP;6a51959d-3042-482d-a784-5ad74718fe44;;PS)
The defaultSecurityDescriptor is represented in the SDDL language and if you want to read more about its syntaxes you can read it here: SDDL Syntax
The changes are marked in Bold and here’s why I made them:
Default (A;;RPCRLCLORCSDDT;;;CO) as you can see the CREATOR_OWNER of the Computer object has Control Access, this means who ever created the object can read the ms-Mcs-AdmPwd attribute (and other attributes with the Confidential data bit). Normally in an enterprise we use SCCM, MDT or some other deployment software. I see no reason for such a service account having that kind of access on all the computer objects. To fix this I remove CR (Control Access).
For my newly created attributes:
(OA;;WP;d8af3522-7870-4e55-bbfe-528dd238a521;;PS)
Object Access;;Write Property;ms-Mcs-AdmPwd;SELF.
(OA;;RPWP;6a51959d-3042-482d-a784-5ad74718fe44;;PS)
Object Access;;Read Property,Write Property;ms-Mcs-AdmPwdExpirationTime;;SELF.
When delegating read/write access using the LAPS PS module it is set at the OU level for the SELF built-in account to inherent down on Computer objects. This is not bad, and it’s a good practice to delegate at the OU level. But in this case, I think it’s important this ACEs is consistent and don’t want to be dependent on inheritance. If the computer object is moved and it losses the access it won’t be able to change the password.
Note: This will only work for newly created Computer objects after the change. We could use a script to set explicit ACEs, or, if possible, reset the security descriptor to default.
The Schema update will run nicely and we can verify the attributes using LDP.
When this is done we can create the Group Policy that will enable and configure the clients to change the password and store it in Active Directory. In the LAPS package we have the ADMX/ADML files and copy them to the Group Policy Central Store (which I hope you have).
In GPMC create a new GPO and navigate to the LAPS settings in Computer Configuration\Policies\Administrative Templates\LAPS\. Configure the settings accordingly to your requirements. When done, link the GPO at the domain root level or where applicable.
Generally speaking, we should avoid linking GPOs at the domain level, but there are a few settings I want to be enabled in the entire domain and LAPS is one of them. But this also depends on how well thought your delegation model is.
In combination with the modified ACLs and this GPO I have the best possible way to ensure a solid implementation of LAPS.
If you don’t want the Domain Controllers to be affected, just don’t install the Client-Side Extension on them, and the policy won’t be applied.
After a GPO refresh we can see this works perfectly.
And just to be sure, we can see the ACE is set with explicit permissions to SELF on the Computer object.
And now to the last part. Delegate the rights to read the ms-Mcs-AdmPwd value.
If you noticed, the ms-Mcs-AdmPwd attribute has the searchFlags: 0x388 = ( PRESERVE_ON_DELETE | CONFIDENTIAL | NEVER_AUDIT_VALUE | RODC_FILTERED );
In this case, the Confidential part is the one I’m interest in. This attribute is flagged as Confidential and it isn’t enough to have Read Properties (Even though people seems to think that for some reason). As I mentioned earlier who ever has Control Access can read the value. If you don’t have it, then it will seem like its empty. Could be quite misleading if you have a script that only looks at ReadProperty (and the absence of effective permissions is another one).
Note: If you need to learn more about Confidential Data bit: https://support.microsoft.com/en-us/kb/922836
Here we can use the LAPS PS module to delegate it at the OU level.
Set-AdmPwdReadPasswordPermission -OrgUnit “OU=Devices,OU=SE,OU=Countries,OU=Corp,DC=ad,DC=secid,DC=se” -AllowedPrincipals ACL-SE-C-LAPS
Set-AdmPwdResetPasswordPermission -OrgUnit “OU=Devices,OU=SE,OU=Countries,OU=Corp,DC=ad,DC=secid,DC=se” -Allowed Principals ACL-SE-C-LAPS
To verify this, we can’t see the Control Access permissions with ADUC. We could use LDP.exe Advanced Security Descriptor to view it.
Note: If any Security Principal has the extended right “All extended rights” (which can be seen in ADUC) they have the Control Access permission as well. One example would be members of the Account Operators Group (do I even need to mention BA,DA,EA?)
Now you maybe think, why not add the delegation part directly to the default security descriptor as well?
Well, maybe there are computer objects in different OUs with different owners that should only be allowed access to read the ms-Mcs-AdmPwd attribute. The computers in other hand, should always be able to update the password.
Well, that’s all folks.
Hi Daniel. This is something really missing from security perspective. Well documented and explained. I love to read documentations like this form people understanding AD.
Thanks a lot for sharing.
You helped me to reduce the time needed to obtain the right values.
Cheers
Tarkan