Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KerberosClient Cache returns ticket of another user #248

Closed
marcelhintermann opened this issue Jun 2, 2021 · 4 comments · Fixed by #249
Closed

KerberosClient Cache returns ticket of another user #248

marcelhintermann opened this issue Jun 2, 2021 · 4 comments · Fixed by #249
Labels

Comments

@marcelhintermann
Copy link

Hi Steve

Many thanks for your great work with this library.
We use it to exchange Kerberos Tickets to JWT-Token (from ADFS) and also the vise versa JWT Token to Kerberos Tickets.

Describe the bug
We found a caching related bug.
If CacheServiceTickets on the KerberosClient is enabled, tickets for wrong users are returned.

To Reproduce

The sample below reproduces the bug.

Setup is a bit complicated, but the scenario is as follows:

A service requests a TGT Ticket on behalf of user for another target service.
On the target service itself, the ticket is decoded with the Authenticator and the according keytab.
This is the place where you can see that it is the wrong ticket.

Commands to create 2 demo users and a service user:

Service User configuration:

New-ADUser `
-Name "svc-top_lab" `
-SamAccountName "svc-top_lab" `
-AccountPassword (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) `
-ChangePasswordAtLogon $False `
-Enabled $True

Set-ADUser -Identity svc-top_lab -ServicePrincipalNames @{Add='HTTP/top.tep-lab.orchid.tribbles.cloud'}
Set-ADAccountControl -Identity svc-top_lab -TrustedForDelegation $false -TrustedToAuthForDelegation $true
Set-ADUser -Identity svc-top_lab -Add @{'msDS-AllowedToDelegateTo'=@('HTTP/api-krb.tep-lab.orchid.tribbles.cloud')}

ktpass.exe -out keytab -mapUser svc-top_lab@tep.local -pass P@ssw0rd -crypto all -ptype KRB5_NT_PRINCIPAL -princ HTTP/api-krb.tep-lab.orchid.tribbles.cloud@tep.local


Demo users:

1..2 | ForEach-Object {New-ADUser -Name user-demo$_ -AccountPassword (ConvertTo-SecureString P@ssw0rd -AsPlainText -Force) -ChangePasswordAtLogon $False -Enabled $True}

Code that reproduces the error:

using System;
using System.IO;
using System.Threading.Tasks;

using Kerberos.NET;
using Kerberos.NET.Client;
using Kerberos.NET.Configuration;
using Kerberos.NET.Credentials;
using Kerberos.NET.Crypto;
using Kerberos.NET.Entities;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;


namespace TEP.Testing.Bug
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);
                builder.AddFilter<ConsoleLoggerProvider>(level => level >= LogLevel.Trace);
            });

            var user1 = "user-demo1@tep.local";
            var user2 = "user-demo2@tep.local";

            var config = Krb5Config.Parse(await File.ReadAllTextAsync("./krb5.conf"));
            
            var serviceCredentials = new KerberosPasswordCredential("svc-top_lab@tep.local", "P@ssw0rd");
            
            // SPN of the target service
            var targetServiceSPN = "HTTP/api-krb.tep-loris.orchid.tribbles.cloud";

            // keytab & authenticator for the target service that would receive the tickets
            var keyTab = new KeyTable(await File.ReadAllBytesAsync("./keytab"));
            var authenticator = new KerberosAuthenticator(keyTab, loggerFactory);


            Console.WriteLine("\n\n\nWith CacheServiceTickets enabled;");
            {
                var s4UClient = new KerberosClient(config, loggerFactory)
                {
                    CacheServiceTickets = true,
                    CacheInMemory = true,
                    RenewTickets = true,
                };

                await s4UClient.Authenticate(serviceCredentials);

                // S4U flow to access a service in behave of a user
                var s4U2SelfUser1Request = await s4UClient.GetServiceTicket(serviceCredentials.UserName, ApOptions.MutualRequired, user1);
                var s4U2ProxyUser1Ticket = await s4UClient.GetServiceTicket(targetServiceSPN, ApOptions.MutualRequired, s4uTicket: s4U2SelfUser1Request.Ticket);

                var s4U2SelfUser2Request = await s4UClient.GetServiceTicket(serviceCredentials.UserName, ApOptions.MutualRequired, user2);
                var s4U2ProxyUser2Ticket = await s4UClient.GetServiceTicket(targetServiceSPN, ApOptions.MutualRequired, s4uTicket: s4U2SelfUser2Request.Ticket);


                // encode & decode tickets and validate them with the keytab of the target service
                var user1TicketBytes = s4U2ProxyUser1Ticket.EncodeGssApi().ToArray();
                var user1Identity = await authenticator.Authenticate(user1TicketBytes);

                var user2TicketBytes = s4U2ProxyUser2Ticket.EncodeGssApi().ToArray();
                var user2Identity = await authenticator.Authenticate(user2TicketBytes);


                Console.WriteLine($"\t Expected Identity: {user1} Identity Ticket: {user1Identity.Name}");

                // user2Identity is the one from user1
                Console.WriteLine($"\t Expected Identity: {user2} Identity Ticket: {user2Identity.Name}");
                if (!user2.Equals(user2Identity.Name))
                {
                    Console.WriteLine("Wrong user in ticket.");
                }
            }

            //In this block everything is okay - if CacheServiceTickets are disabled the result is as expected
            Console.WriteLine("With CacheServiceTickets disabled;");
            {
                var s4UClient = new KerberosClient(config, loggerFactory)
                {
                    CacheServiceTickets = false,
                    CacheInMemory = true,
                    RenewTickets = true,
                };

                await s4UClient.Authenticate(serviceCredentials);

                var s4U2SelfUser1Request = await s4UClient.GetServiceTicket(serviceCredentials.UserName, ApOptions.MutualRequired, user1);
                var s4U2ProxyUser1Ticket = await s4UClient.GetServiceTicket(targetServiceSPN, ApOptions.MutualRequired, s4uTicket: s4U2SelfUser1Request.Ticket);
                
                var s4U2SelfUser2Request = await s4UClient.GetServiceTicket(serviceCredentials.UserName, ApOptions.MutualRequired, user2);
                var s4U2ProxyUser2Ticket = await s4UClient.GetServiceTicket(targetServiceSPN, ApOptions.MutualRequired, s4uTicket: s4U2SelfUser2Request.Ticket);
                
                
                var user1TicketBytes = s4U2ProxyUser1Ticket.EncodeGssApi().ToArray();
                var user1Identity = await authenticator.Authenticate(user1TicketBytes);
                
                var user2TicketBytes = s4U2ProxyUser2Ticket.EncodeGssApi().ToArray();
                var user2Identity = await authenticator.Authenticate(user2TicketBytes);
                

                Console.WriteLine($"\t Expected Identity: {user1} Identity Ticket: {user1Identity.Name}");
                Console.WriteLine($"\t Expected Identity: {user2} Identity Ticket: {user2Identity.Name}");
            }

           

            Console.ReadLine();
        }

    }
}
<img width="682" alt="Screenshot 2021-06-02 at 17 19 37" src="https://user-images.githubusercontent.com/343776/120513033-24053d80-c3cc-11eb-8ab9-2ab14fcdcead.png">

Console output looks like this:

With CacheServiceTickets enabled;
trce: Kerberos.NET.Client.KerberosClient[0]
      Attempting AS-REQ. UserName = svc-top_lab; Domain = TEP.LOCAL; Nonce = -768730279
trce: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connecting to dc.tep.local on port 88
dbug: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connected to dc.tep.local on port 88
dbug: Kerberos.NET.Client.KerberosClient[0]
      AS-REP PA-Data: EType = AES256_CTS_HMAC_SHA1_96; Salt = TEP.LOCALHTTPapi-krb.tep-lab.orchid.tribbles.cloud;
dbug: Kerberos.NET.Client.KerberosClient[0]
      AS-REP PA-Data: EType = RC4_HMAC_NT; Salt = (null);
trce: Kerberos.NET.Client.KerberosClient[0]
      Attempting AS-REQ. UserName = svc-top_lab; Domain = TEP.LOCAL; Nonce = -639463043
trce: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connecting to dc.tep.local on port 88
dbug: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connected to dc.tep.local on port 88
dbug: Kerberos.NET.Client.KerberosClient[0]
      EncPart expected to be KrbEncAsRepPart and is actually KrbEncAsRepPart
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos--krbtgt/tep.local with renewal option until 06/03/2021 15:35:33 +00:00
info: Kerberos.NET.Client.KerberosClient[0]
      Requesting TGS for svc-top_lab; TGT Realm = TEP.LOCAL; TGT Service = krbtgt/TEP.LOCAL; S4U = user-demo1@tep.local; S4UTicket = (null)
trce: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connecting to dc.tep.local on port 88
dbug: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connected to dc.tep.local on port 88
info: Kerberos.NET.Client.KerberosClient[0]
      TGS-REP for svc-top_lab
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos-user-demo1@tep.local-svc-top_lab with renewal option until 06/03/2021 15:35:33 +00:00
info: Kerberos.NET.Client.KerberosClient[0]
      Requesting TGS for HTTP/api-krb.tep-loris.orchid.tribbles.cloud; TGT Realm = TEP.LOCAL; TGT Service = krbtgt/TEP.LOCAL; S4U = (null); S4UTicket = Kerberos.NET.Entities.KrbPrincipalName
trce: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connecting to dc.tep.local on port 88
dbug: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connected to dc.tep.local on port 88
info: Kerberos.NET.Client.KerberosClient[0]
      TGS-REP for HTTP/api-krb.tep-loris.orchid.tribbles.cloud
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos--http/api-krb.tep-loris.orchid.tribbles.cloud with renewal option until 06/03/2021 15:35:33 +00:00
info: Kerberos.NET.Client.KerberosClient[0]
      Requesting TGS for svc-top_lab; TGT Realm = TEP.LOCAL; TGT Service = krbtgt/TEP.LOCAL; S4U = user-demo2@tep.local; S4UTicket = (null)
trce: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connecting to dc.tep.local on port 88
dbug: Kerberos.NET.Transport.TcpKerberosTransport[0]
      TCP connected to dc.tep.local on port 88
info: Kerberos.NET.Client.KerberosClient[0]
      TGS-REP for svc-top_lab
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos-user-demo2@tep.local-svc-top_lab with renewal option until 06/03/2021 15:35:33 +00:00
trce: Kerberos.NET.KerberosValidator[0]
      Validating Kerberos request NegTokenInit Oid: ;
trce: Kerberos.NET.KerberosValidator[0]
      Kerberos request decrypted HTTP/api-krb.tep-loris.orchid.tribbles.cloud
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos-b772fd429e857477071270c3569420eb44187782516464cc76e91e8e4e78bced-89a503cf9f0f22b3b318fe08ff8d62cc1553d1578321445299e3142ab2e841d8 with renewal option until (null)
trce: Kerberos.NET.KerberosValidator[0]
      Validating Kerberos request NegTokenInit Oid: ;
         Expected Identity: user-demo1@tep.local Identity Ticket: user-demo1@tep.local
trce: Kerberos.NET.KerberosValidator[0]
      Kerberos request decrypted HTTP/api-krb.tep-loris.orchid.tribbles.cloud
trce: Kerberos.NET.TicketCacheBase[0]
      Caching ticket until 06/03/2021 01:35:33 +00:00 for kerberos-b772fd429e857477071270c3569420eb44187782516464cc76e91e8e4e78bced-441550f47b514b4938e102c3148511fc41a096204929d6994086db4f981cc5c6 with renewal option until (null)
         Expected Identity: user-demo2@tep.local Identity Ticket: user-demo1@tep.local
Wrong user in ticket.

Expected behavior

I expect that the user the ticket is requested for, is acutally in the ticket.

It works when CacheServiceTickets = false is set to false:

var s4UClient = new KerberosClient(config, loggerFactory)
{
CacheServiceTickets = false,
CacheInMemory = true,
RenewTickets = true,
};

Screenshots
The cache entries are looking good in my opinion:
Screenshot Cache Entries

Let me know if I can help to solve the issue.

Best regards,
Marcel

@SteveSyfuhs
Copy link
Collaborator

Well that's not good. Let me set this up locally and try and repro it. Thanks for the incredibly detailed steps.

@marcelhintermann
Copy link
Author

Thanks for the fast answer.
If it helps, I could provide you access to our DEV environment via remote desktop. Let me know...

@SteveSyfuhs
Copy link
Collaborator

Is there anything interesting in krb5.conf or is it a blank file?

@SteveSyfuhs
Copy link
Collaborator

Easy fix in #249.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants