Friday, December 30, 2016

gMSAs are a Little Bit Weird

In my last post on retrieving the Group Managed Service Account password, I covered the fact that there is an old password value and a current password value. I have found no documentation on why they both exist. I did testing a while back and confirmed that only one was valid. This seemed odd and didn’t make sense in the context of a large enterprise. If a gMSA is used all over the globe in different AD sites, how do you make sure that all the services using the account rotate at the same moment. You don’t. While password changes use urgent replication and replicate to the PDCe immediately, this may still not be sufficient. There are two time intervals listed in the msDS-ManagedPassword explanation:
·         QueryPasswordInterval (8 bytes): A 64-bit unsigned integer containing the length of time, in units of 10^(-7) seconds, after which the receiver must requery the password. The QueryPasswordInterval field MUST be placed on a 64-bit boundary.
·         UnchangedPasswordInterval (8 bytes): A 64-bit unsigned integer containing the length of time, in units of 10^(-7) seconds, before which password queries will always return this password value. The UnchangedPasswordInterval field MUST be placed on a 64-bit boundary.

I was unable to decipher what exactly those mean, in terms of the password change, so I decided to see what happened by querying the blob repeatedly before, during, and after the change. While doing this, I tested LDAP binds for each password and recorded the results, as well as the key version number (KVNO). The KVNO is simply the version number for the password.
It is nearly impossible for the gMSA, and all the services running as it, to rotate at the same movement.  It is impossible, in an enterprise, to even get the new password everywhere instantly. Instead AD pre-stages the new password. This is why the “must requery” time is always 5 minutes before the password change and both passwords are accepted for 5 minutes after the change. The test data below shows exactly what happens.  
TestTime
OldPwd
NewPwd
KVNO
NextQuery
PwdGoodUntil
12/29/2016 19:53
FALSE
TRUE
2
12/29/2016 19:59
12/29/2016 19:54
12/29/2016 19:54
FALSE
TRUE
2
12/29/2016 19:59
12/29/2016 19:54
12/29/2016 19:54
FALSE
TRUE
2
12/29/2016 19:59
12/29/2016 19:54
12/29/2016 19:55
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 19:55
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 19:56
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 19:57
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 19:58
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 19:59
TRUE
FALSE
2
12/29/2016 19:59
12/30/2016 15:54
12/29/2016 20:00
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:00
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:01
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:02
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:03
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:04
TRUE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:05
FALSE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:05
FALSE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:06
FALSE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54
12/29/2016 20:06
FALSE
TRUE
3
12/30/2016 15:59
12/30/2016 15:54

This mostly makes sense, except that in the 5 minutes prior to the password change, the value clearly called CurrentPassword, is wrong. It has shifted to the PreviousPassword value. Also, the account is set to a one day password change interval, yet the next change is not in 24 hours, it is in 20 hours. If you decide to use the code to implement your own use of a gMSA it is vital to keep these things in mind.  I think this is all explained by Microsoft here, but it is hard to read.  It makes more sense after having run the test.



Wednesday, December 28, 2016

Any sufficiently advanced Active Directory Web Service is indistinguishable from magic

Or how to retrieve the password of a Group Managed Service Account

I’m not one to leave things alone if I feel like I don’t have a solid and deep understanding of them, well that or I completely ignore them… In the case of the Active Directory Web Service I have always ignored it and been confused why it existed or what it did. As far as I could tell, it created more attack surface on DCs and extra dependencies. As far as I had seen, the ADWS did a poor job retrieving data I have always gotten in other ways in the past. It limits result set sizes and other odd things. The one thought I had was, “maybe the ADWS does less than LDAP or ADSI, so one can block those in certain situations”. That would be a nice security control. It turns out that the ADWS lets you do just about anything that can be done via LDAP v3, including the use of extended LDAP operations. At this point, I assumed that the ADWS was fully redundant to the LDAP and ADSI interfaces. Then I found something that “can only be done via the ADWS”, interaction with Group Managed Service Accounts.

Group Managed Service accounts, or gMSAs, are the new hotness in service accounts.  By new I mean 2012. The gMSA is a service account that auto rotates its password and works with services that support it, so that the service and AD rotate the password at the same moment. Microsoft first introduced the managed service account (notice the missing word group), which was a great idea, but didn’t meet the needs of an enterprise. The old managed service account could only run on a single server and wasn’t well supported by various services. The gMSA can run on any number of servers, allowing for the account to hold a Service Principal Name (SPN), which is key to the service working with Kerberos. While Kerberos is supposed to be the default auth method for Windows, since 2000, a huge number of systems may fall back to NTLM if Kerberos is misconfigured. As I’ve noted before, NTLM comes in flavors of bad and worse. gMSAs help deal with this while removing the overhead of manual service account password changes.

Here’s where the learning began for me. Not all services and systems can work using a gMSA. I’m not clear why this is the case when something simply runs as a service, but I am sure there are good reasons. Possibly it could be things like DPAPI encryption, though the gMSA stores, and returns, two versions of the password, so DPAPI shouldn’t be an issue. None the less, I decided I’d just write my own .NET tools to retrieve the password and do the needful.

The Trail of Failure

The gMSA password is stored in the attribute msDS-ManagedPassword. Also, here. So, I created a gMSA and tried to retrieve the password using LDAP, via ldifde. Then the sadness started. I tried to pull back the samaccountname and password blob. Yes, like a computer object the tools add a trailing $ to the samaccountname.

C:\Windows\system32>ldifde -f ssss  -l msDS-ManagedPassword,samaccountname -r "samaccountname=_coolaccount$"

Connecting to "fundc1.xcloud.gbl"

Logging in as current user using SSPI

Exporting directory to file ssss

Searching for entries...

Writing out entries

No Entries found

 

The command has completed successfully

 

C:\Windows\system32>

 

I tried again, leaving out the password.

C:\Windows\system32>ldifde -f ssss  -l samaccountname -r "samaccountname=_coolaccount$"

Connecting to "fundc1.xcloud.gbl"

Logging in as current user using SSPI

Exporting directory to file ssss

Searching for entries...

Writing out entries.

1 entries exported

 

The command has completed successfully

 

C:\Windows\system32>

 

This was odd so I decided to get more sophisticated by running the command in .NET code and failed.  I always use the System.DirectoryServices.Protocols (S,DS.P) namespace, as it calls the native LDAP libraries with no interference by the ADSI layer. It also works great with 3rd party LDAPs. 

I did a search request similar to this:

SearchRequest searchRequest = new SearchRequest

                                    (targetOu,

                                      ldapSearchFilter,

                                      SearchScope.OneLevel,

                                      strArrayOfAttributes);

 

I still got nothing back and be began to suspect that there was a mystery afoot.  I then tired via PowerShell.

PS C:\Users\whataname> Get-ADServiceAccount -Identity _coolaccount2 -Properties 'msDS-ManagedPassword'

 

 

DistinguishedName    : CN=_coolaccount2,CN=Managed Service Accounts,DC=xcloud,DC=gbl

Enabled              : True

msDS-ManagedPassword : {1, 0, 0, 0...}

Name                 : _coolaccount2

ObjectClass          : msDS-GroupManagedServiceAccount

ObjectGUID           : 380a65ba-fb12-491f-ac4f-775f0eb7ceba

SamAccountName       : _coolaccount2$

SID                  : S-1-5-21-2885290700-3155337720-2128568531-2106

UserPrincipalName    :

 

 

 

PS C:\Users\whataname>

 

I got back the password blob and did a small dance, then I went to work on figuring out how PowerShell got the password when I was unable to. I started a packet capture using Netmon 3.4, Which showed me that PowerShell was reaching out to a DC on TCP port 9389. This is the port for the ADWS. I did much searching and tried several tools, such as Fiddler, to let me see inside the AWDS traffic. None of these worked.

On the Road to Successville

Finally I found a great article boasting exactly what I needed, How to view SOAP XML messages to and from AD Webservices and Powershell. This was the cure I was looking for. I setup tracing and then created a new gMSA, and then used the PowerShell command to retrieve the password blob.

In both the creation and the retrieval of the password, I found this:

<ad:controls>

<ad:control type="1.2.840.113556.1.4.801" criticality="true">

<ad:controlValue xsi:type="xsd:base64Binary">MIQAAAADAgEH</ad:controlValue>

</ad:control>

</ad:controls>

If you do a lot of AD work, 1.2.840.113556.1.4.801 pops out as an LDAP Extended Control OID. In this case, it is LDAP_SERVER_SD_FLAGS_OID control that lets one send and receive Security Descriptors. The controlValue ‘30 84 00 00 00 03 02 01 07’ is the BER encoded ASN.1  value of 07 which gets us the user owner, group owner, and DACL.

With this information, I realized I couldn’t use ldifde, as it does not let you specify LDAP Controls. The next step was to use S.DS.P to issue the search request with the LDAP Control.  I had a well-built project using S.DS.P, that implemented a control already, so rather than rewriting it all, I just cut and pasted a bit and requested the password blob and it worked!

With the assurance that I could pull back the password blob, I went about making a cleaner implementation than my borrowed code. I also went about writing a class to decode the password blob.

As the passwords are random, the chars map to mostly non-standard characters. Using the password in an LDAP bind confirms the password is good.

My leaner and cleaner implementation for retrieving the password blob did not go so well. I repeatedly got back {"An operation error occurred."}. This occurred even with strong LDAP focused error handling.  It left me with very little to go on.

When I took a close look at the different code, I noticed that the working code explicitly setup my LdapConnection securely:

            theConn.SessionOptions.Sealing = true;

            theConn.SessionOptions.Signing = true;

 

The default options set signing to true, but sealing to false. 

This means that the connection, by default, has integrity (tamper) protection, but the data is not encrypted, so anyone who can sniff it can read it.  It makes perfect sense to not send back an unencrypted password over and unencrypted channel. In fact, password changes are also not allowed over unencrypted connections, by default.  Knowing that encryption is required it was easy to find the ldifde syntax to encrypt and get back the password blob.

-h              Enable SASL layer signing and encryption

 

C:\Windows\System32\pwdb>ldifde -f ssss  -l msDS-ManagedPassword,samaccountname -r "samaccountname=_coolaccount$" -h

Connecting to "fundc1.xcloud.gbl"

Logging in as current user using SSPI

Exporting directory to file ssss

Searching for entries...

Writing out entries.

1 entries exported

 

The command has completed successfully

 

C:\Windows\System32\pwdb>type ssss

dn: CN=_coolaccount,CN=Managed Service Accounts,DC=xcloud,DC=gbl

changetype: add

sAMAccountName: _coolaccount$

msDS-ManagedPassword::

 AQAAACIBAAAQAAAAEgEaASJTvMGeMn+PpMxf+1PtLESZoQu7OKmjqR1ndZIRZf821rjx0KNBvR2kii

 2Jadwe5km9fZFrejLezkuzznJ5VeHAaHuNEklQgwNd8vMjpXYwMc13jMGSxnRoT3M727mn4EL887Dj

 RjGyhKsN7Jg1vPGIl7ZQXspR8vfU09Oo26EkMsPw67NNrDyZLRdSLjMvaZ3jSh6wfFqH5IaKMevSs2

 ouDrj3dTsx63lrj/bXuQ1XMrH39ChcZ07qkIAW/F8dDAdjGVSMPFusmP+WqyrotDS8s7NYpVVNJPV0

 nP+CnBePs+38WCgG0cJBfZCv4bfPZELYJb8x9UX9FiD+D5LHVEAAAPIH4veYFgAA8qkRRZgWAAA=

 

 

C:\Windows\System32\pwdb>

 

Sure enough, buy adding signing and sealing to my new project, I was able to get back the blob. This spurred the question, “what’s up with the LDAP_SERVER_SD_FLAGS_OID?”  I commented out the LDAP Control and was still able to get the password back.

Soooo, what is up with the LDAP_SERVER_SD_FLAGS_OID?  It turns out, that for some unknown reason, the principals that can retrieve the password are not stored as part of a standard ACE, nor as some list of SIDs, but instead, the list is stored as a binary blob in security descriptor format on the attribute msDS-GroupMSAMembership. Like other security descriptors, the LDAP Control must be present to read or write to it.

To read the SD, the query is formed like this:

 

                string ldapSearchFilter = string.Format("(|(sAMAccountName={0})(sAMAccountName={0}$))", acctName);

                string[] attribs = new string[] { "name",

                            "msDS-GroupMSAMembership"                           };

 

                // create a SearchRequest object

                SearchRequest searchRequest = new SearchRequest

                                                (this.DomainDN, ldapSearchFilter,

                                                 System.DirectoryServices.Protocols.SearchScope.Subtree, attribs);

 

                byte[] OIDVal = new byte[] { 0x30, 0x84, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x07 };

                DirectoryControl editSDOid = new DirectoryControl("1.2.840.113556.1.4.801", OIDVal, false, true);

                searchRequest.Controls.Add(editSDOid);

 

In order to read and write the security descriptors, one can use the System.DirectoryServices.ActiveDirectorySecurity class. Get the byte[] value of  msDS-GroupMSAMembership and pass it into that class like so:

public byte[] addSIDtoSD(byte[] currentSddlBytes, string newSID)

        {

            ActiveDirectorySecurity ads = new ActiveDirectorySecurity();

            SecurityIdentifier realSID = new SecurityIdentifier(newSID);

 

            byte[] sddlOut = new byte[2];

 

            try

            {

                ads.SetSecurityDescriptorBinaryForm(currentSddlBytes);

                AuthorizationRuleCollection bb = ads.GetAccessRules(true, true, typeof(SecurityIdentifier));

                bool bAlreadyInSD = false;

 

                //skip the add if the SID is already on the list

                foreach (AuthorizationRule ar in bb)

                {

 

                    if (ar.IdentityReference.Value.ToString() == realSID.ToString())

                    {

                        bAlreadyInSD = true;

                        break;

                    }

                }

 

                if (!bAlreadyInSD)

                {

                    //add it to the SD

                    ads.AddAccessRule(new ActiveDirectoryAccessRule(realSID, ActiveDirectoryRights.GenericAll, AccessControlType.Allow));

 

                    //output the new SD in bytes

                    sddlOut = ads.GetSecurityDescriptorBinaryForm();

 

One benefit of doing this in .NET is that the New-ADServiceAccount and Set-ADServiceAccount commands only let you overwrite the list of PrincipalsAllowedToRetrieveManagedPassword, so it is more work to add or remove a user or group. Also, the underlying PowerShell will not allow mixing the list so that it contains users and groups.

Set-ADServiceAccount : An AD Property Value Collection may only contain values of the same type. Specified value of

type 'Microsoft.ActiveDirectory.Management.ADGroup' does not match the existing type of

'Microsoft.ActiveDirectory.Management.ADUser'.

Parameter name: value

At line:1 char:1

+ Set-ADServiceAccount -Identity _coolaccount -PrincipalsAllowedToRetrieveManagedP ...

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : InvalidArgument: (_coolaccount:ADServiceAccount) [Set-ADServiceAccount], ArgumentExcepti

   on

    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Comm

   ands.SetADServiceAccount

 

There is no such constraint in AD, so using .NET code you can avoid breaking your account.

What it’s Like in Successville (Summary?)

The Active Directory Web Service, like all magic, just uses some sleight of hand and misdirection with gMSAs.

You simply need to request the password blob using SSL or SASL encryption. No LDAP Controls are needed to read it.

You can view or edit the msDS-GroupMSAMembership list if you know how to work with security descripts and include the LDAP Extended Control 1.2.840.113556.1.4.801 LDAP_SERVER_SD_FLAGS_OID.

Cleaner code can be found here.

 

 

Tuesday, December 6, 2016

The Strange Case of John Legere's Alien Abduction

From the first time I talked to John Legere face to face, I felt there was an "other worldly" quality to him. He seemed to radiate some sort of power.

Based on T-Mobile's quarter over quarter performance since his taking the reins, I think it is pretty clear what is going on...


I suspect that John Legere was abducted by aliens and subjected to some sort of enhancements. 

Ask the stockholders, this is not a bad thing, just an observation. 

I would be willing to entertain an evil twin theory or something like that movie Dave, but my money is on alien abduction. 

While this may seem far fetched, take a look at this video from 2007ish. 




Is this the same guy as the one below?  Sure, they look and sound the same, but that is where it ends.