Working with gMSAs in System.DirectoryServices.Protocols

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.DirectoryServices.Protocols;

using System.DirectoryServices;

using System.IO;

using System.Security.AccessControl;

using System.Security.Principal;

 

namespace gMSAtemP

{

    class LDAP

    {

        public LdapConnection theConn;

        public string SchemaDN;

        public string ConfigDN;

        public string DomainDN;

        public string dnsHostName;

        public string ntDsDsa;

        public string DSserverName;

        public List<string> AppPartitions = new List<string>();

        public bool bIsADLDS = false;

 

        //we assume defalt query policy and get the datas...  //this requires auth  =(

        public int MaxValRange = 1500;

        public int MaxPageSize = 1000;

 

 

        public LDAP(LdapDirectoryIdentifier inConn)

        {

 

            theConn = new LdapConnection(inConn);

            theConn.AuthType = AuthType.Anonymous; //anon needed to get rootDSE

 

            theConn.Timeout = new TimeSpan(0, 15, 0);

            getRootDSEData();

 

            //switch to an auth'd context

            theConn.Dispose();

            theConn = new LdapConnection(inConn);

            //add creds

            theConn.Credential = new System.Net.NetworkCredential("whataname", "Th1sisaGreatword", "XCLOUD");

            theConn.AuthType = AuthType.Negotiate;

            theConn.SessionOptions.RootDseCache = true;

            theConn.Timeout = new TimeSpan(0, 15, 0);

            theConn.SessionOptions.SendTimeout = new TimeSpan(0, 15, 0);

            theConn.SessionOptions.ReferralChasing = ReferralChasingOptions.None;

            theConn.SessionOptions.Signing = true;

            theConn.SessionOptions.Sealing = true; //the key to getting back password blobs and encryption...

            try

            {

                theConn.Bind();

            }

            catch (Exception ex)

            {

 

            }

        }

 

        /// <summary>

        /// Adds a new SID to the SD that controls access to read the password blob

        /// </summary>

        /// <param name="currentSddlBytes">current SDDL SD</param>

        /// <param name="newSID">sid as string, SDDL form</param>

        /// <returns>bigger SD bytes</returns>

        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();

                }

            }

            catch (Exception ex)

            {

                throw ex;

            }

 

            return sddlOut;

        }

 

 

        /// <summary>

        /// Gets the SD holding those who can read the password blob

        /// </summary>

        /// <param name="acctName">samaccountname</param>

        /// <returns>binary SD</returns>

        public byte[] getgMSAPwdReaders(string acctName)

        {

            try

            {

 

                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);

 

                //      30 84 00 00 00 03 02 01 07

                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);

 

                //for really slow DCs

                searchRequest.TimeLimit = new TimeSpan(0, 5, 0);

 

                // cast the directory response into a

                // SearchResponse object

                SearchResponse searchResponse =

                    (SearchResponse)theConn.SendRequest(searchRequest);

 

                if (searchResponse.Entries.Count == 0)

                {

                    return null;

                }

 

                // display the entries within this page

                foreach (SearchResultEntry entry in searchResponse.Entries)

                {

                    SearchResultAttributeCollection attributes = entry.Attributes;

 

                    foreach (DirectoryAttribute currentAtt in attributes.Values)

                    {

                        string attName = currentAtt.Name.ToLower();

 

                        switch (attName)

                        {

 

                            case "msds-groupmsamembership":

                                byte[] sdsad = (byte[])currentAtt[0];

                                return sdsad;

                                break;

                        }

                    }

                }

            }

            catch (DirectoryOperationException odxe)

            {

                throw odxe;

            }

 

            catch (DirectoryException dex)

            {

                throw dex;

            }

 

            catch (Exception e)

            {

                throw e;

            }

 

            return null;

 

        }

 

        /// <summary>

        /// Sets the new security descriptor on an account

        /// </summary>

        /// <param name="acctName">samaccountname</param>

        /// <param name="newSD">binary SD</param>

        /// <returns>true/false</returns>

        public bool setgMSAPwdReaders(string acctName, byte[] newSD)

        {

            string dn = getDNFromSamAccountName(acctName);

            if (dn != "")

            {

 

                //make and send req

                ModifyRequest mReq = new ModifyRequest(dn, DirectoryAttributeOperation.Replace, "msDS-GroupMSAMembership", newSD);

 

                try

                {

                    ModifyResponse mResp = (ModifyResponse)theConn.SendRequest(mReq);

                    if (mResp.ResultCode == ResultCode.Success)

                    {

                        return true;

                    }

                }

                catch (DirectoryOperationException dox)

                {

                    throw dox;

                }

                catch (Exception ex)

                {

                    throw ex;

                }

            }

 

            return false;

        }

 

 

        /// <summary>

        /// Gets the DN of an object by samAccountName.  Looks with and without the trailing$

        /// </summary>

        /// <param name="acctName"></param>

        /// <returns>DN</returns>

        public string getDNFromSamAccountName(string acctName)

        {

            try

            {

                //look for accoutn with or without $

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

                SearchRequest sr = new SearchRequest(this.DomainDN,

                    ldapSearchFilter,

                    System.DirectoryServices.Protocols.SearchScope.Subtree,

                    new string[] { "samaccountname" });

 

                SearchResponse sresp = (SearchResponse)theConn.SendRequest(sr);

                if (sresp.Entries.Count > 0)

                {

                    return sresp.Entries[0].DistinguishedName;

                }

 

            }

            catch (Exception ex)

            {

                throw ex;

            }

 

            return "";

 

        }

 

        /// <summary>

        /// get the AD naming contexts from RootDSE, and otehr useful data

        /// </summary>

        private void getRootDSEData()

        {

            try

            {

 

 

                // this search filter does not limit the returned results

                string ldapSearchFilter = "(objectClass=*)";

 

                SearchRequest searchRequest = new SearchRequest(null,

                                                 ldapSearchFilter, System.DirectoryServices.Protocols.SearchScope.Base, null);

                             

                // cast the directory response into a

                // SearchResponse object

                SearchResponse searchResponse =

                    (SearchResponse)theConn.SendRequest(searchRequest);

 

 

                // display the entries within this page

                foreach (SearchResultEntry entry in searchResponse.Entries)

                {

 

                    // start getting attribs from objects in the page

                    SearchResultAttributeCollection attributes = entry.Attributes;

 

                    foreach (DirectoryAttribute currentsn in attributes.Values)

                    {

                        string dn = entry.DistinguishedName;

                        string attribName = currentsn.Name;

 

                        if (attribName == "defaultNamingContext")

                        {

                            DomainDN = currentsn[0].ToString();

                        }

                        if (attribName.ToLower() == "dnshostname")

                        {

                            dnsHostName = currentsn[0].ToString();

                        }

                        if (attribName == "configurationNamingContext")

                        {

                            ConfigDN = currentsn[0].ToString();

                        }

 

                        if (attribName == "schemaNamingContext")

                        {

                            SchemaDN = currentsn[0].ToString();

                        }

 

                        if (attribName == "namingContexts")

                        {

                            string[] vals = (string[])currentsn.GetValues(typeof(string));

                            AppPartitions.AddRange(vals);

                        }

 

                        //supportedCapabilities try to ID LDS vs AD

                        if (attribName == "supportedCapabilities")

                        {

                            string[] vals = (string[])currentsn.GetValues(typeof(string));

                            if (vals.Contains("1.2.840.113556.1.4.1851"))

                            {

                                bIsADLDS = true;

                            }

                        }

 

                        //dsServiceName

                        if (attribName == "dsServiceName")

                        {

                            ntDsDsa = currentsn[0].ToString();

                        }

 

                        // serverName

                        if (attribName == "serverName")

                        {

                            DSserverName = currentsn[0].ToString();

                        }

                    }

                }

            }

            catch (DirectoryException dex)

            {

                throw dex;

            }

            catch (Exception e)

            {

                Console.WriteLine("\nDuring get naming Contexts, Unexpected exception occured:\n\t{0}: {1}",

                                  e.GetType().Name, e.Message);

 

                if (e.Message == "The LDAP server is unavailable.")

                {

                    Console.WriteLine("Probably no path to the server");

                    throw e;

                }

                throw e;

            }

            finally

            {

                AppPartitions.Remove(ConfigDN);

                AppPartitions.Remove(SchemaDN);

            }

 

        }

 

        /// <summary>

        /// Get the password blob

        /// </summary>

        /// <param name="acctName">samAccountName</param>

        /// <returns>blob</returns>

        public byte[] getgMSAPwdBlob(string acctName)

        {

            try

            {

                //this is the filter that the ADWS issues , so why not??

                string ldapSearchFilter = "(&(|(sAMAccountName=" + acctName + ")(sAMAccountName=" + acctName + "$))(|(&(objectClass=msDS-ManagedServiceAccount)(objectCategory=msDS-ManagedServiceAccount))(objectClass=msDS-GroupManagedServiceAccount)))"; //we get all computers, just in case

                string[] attribs = new string[] { "name", "msDS-ManagedPassword" };

 

                // create a SearchRequest object

                SearchRequest searchRequest = new SearchRequest

                                                (this.DomainDN, ldapSearchFilter,

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

 

                //not needed!

                //     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);

 

                //for really slow DCs

                searchRequest.TimeLimit = new TimeSpan(0, 5, 0);

 

                // cast the directory response into a

                // SearchResponse object

                SearchResponse searchResponse =

                    (SearchResponse)theConn.SendRequest(searchRequest);

 

                if (searchResponse.Entries.Count == 0)

                {

                    return null;

                }

 

                // display the entries within this result

                foreach (SearchResultEntry entry in searchResponse.Entries)

                {

                    SearchResultAttributeCollection attributes = entry.Attributes;

 

                    foreach (DirectoryAttribute currentAtt in attributes.Values)

                    {

                        string attName = currentAtt.Name.ToLower();

                        switch (attName)

                        {

                            //msDS-ManagedPassword

                            case "msds-managedpassword":

                                byte[] sdsad = (byte[])currentAtt[0];

 

                                return sdsad;

                                break;

 

                        }

                    }

                }

            }

            catch (DirectoryOperationException odxe)

            {

                throw odxe;

            }

 

            catch (DirectoryException dex)

            {

                throw dex;

            }

            catch (Exception e)

            {

                throw e;

            }

 

            return null;

 

        }

 

 

        /// <summary>

        /// Test an AD account password

        /// </summary>

        /// <param name="ldi">an LdapDirectoryIdentifier</param>

        /// <param name="userName">name of user</param>

        /// <param name="password">the password</param>

        /// <param name="domain">the domain name</param>

        /// <returns>true/false</returns>

        public static bool testPassword(LdapDirectoryIdentifier ldi, string userName, string password, string domain)

        {

            LdapConnection tConn = new LdapConnection(ldi);

 

            try

            {

 

                tConn.Credential = new System.Net.NetworkCredential(userName + "$", password, domain);

                tConn.Bind();

                tConn.Dispose();

                return true;

            }

            catch (Exception ex)

            {

 

            }

            tConn.Dispose();

            return false;

        }

 

    }

}

2 comments:

Unknown said...

Where/How did you determine the magic numbers? As in, the magic OIDval bytes. Currently I've been using byte[] ctrlData = BerConverter.Encode("{i}", new object[] { 1 });

So I get an byte array of: { 0x30, 0x84, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x01 };

Whereas yours appears to be using the number 7.

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

I am dealing with LDAP_SERVER_RANGE_OPTION_OID ( 1.2.840.113556.1.4.802 ) and seem to have it working and am guessing you used the number 7 because it gives the enum mask of:

AccessControlSections accessSectionMask = AccessControlSections.Access | AccessControlSections.Audit | AccessControlSections.Owner;

Am I making any sense?

Unknown said...

Ah, seems I was correct about the value being the flags, though it retrieves Group instead of SACL/Audit information:
3.1.1.3.4.1.11 LDAP_SERVER_SD_FLAGS_OID
https://msdn.microsoft.com/en-us/library/cc223323.aspx


And as for my, appears it is discarded, which is good to know.
3.1.1.3.4.1.22 LDAP_SERVER_RANGE_RETRIEVAL_NOERR_OID
https://msdn.microsoft.com/en-us/library/cc223345.aspx

Inputting falsified referrals to this site violates the terms of service of this site and is considered unauthorized access (hacking).