Search code examples
c#kerberoswindows-identity

Client/Server app, how to create process on remote system as a domain user without transferring that users username/password to the remote system?


I have two systems both running my C# client/server software code. I want to from Computer 1 create a process as a given Active Directory domain user on Computer 2 without having to have my client/server software send the plain text username and password of the AD user from Computer 1 to Computer 2.

The closest I have gotten is it seemed like I could use the function KerberosRequestorSecurityToken on Computer 1 to generate a kerberos ticket and then send that byte[] result via my client/server code to Computer 2 where it should then be able to call KerberosReceiverSecurityToken against the byte[] kerberos ticket that was passed. If all that works then KerberosReceiverSecurityToken would have a WindowsIdentity property that I could then use to create a process on Computer 2 with the user account initially specified on Computer 1 via the KerberosRequestorSecurityToken flow.

I need to use existing Domain User accounts that I want to impersonate vs. registering a service account SPN. This is the crux of the issue that I failed to mention when I originally posted the question.

I cannot seem to get this to work but more so I do not even know if this is actually possible - e.g. should these APIs even allow me to do what I want?

Computer 1 code to generate kerberos ticket. The byte[] result of this is sent via my client/server app to Computer 2.

public static byte[] GetRequestToken(string userName, string password, string domain)
{
    using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
    {
        using (var foundUser = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName))
        {
            Console.WriteLine("User Principal name" + UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName).UserPrincipalName);
            string spn = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName).UserPrincipalName;
            KerberosSecurityTokenProvider k1 = new KerberosSecurityTokenProvider(spn, TokenImpersonationLevel.Impersonation, new NetworkCredential(userName, password, domain));
            KerberosRequestorSecurityToken T1 = k1.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
            var req = T1.GetRequest();
            return req;
        }
    }
}

Computer 2 takes that resulting byte[] kerberos ticket and attempts to recieve it to access a WindowsIdentity but it throws an exception saying invalid logon.

KerberosReceiverSecurityToken receiverToken = new KerberosReceiverSecurityToken(requestToken);
byte[] receiverTokenTicket = receiverToken.GetRequest();

//Identity for impersonation
var impersonatedIdentity = receiverToken.WindowsIdentity;

Solution

  • The origin of the first block of code is functionally wrong. The SPN must be the target receiving the ticket, not the user requesting the ticket. So you need to do the following things...

    1. Register a service principal account (user/computer) in AD.
    2. Add a service principal name to that account of the form foo/host.domain.com where foo is your service name, e.g. http or host, or myapp.
    3. Client needs to request a ticket to foo/host.domain.com
    4. KerberosReceiverSecurityToken should parse that ticket

    Now you have an identity of some varying impersonation level. That impersonation level dictates whether you can start a process as that user. As it happens... you just can't because that's not how Windows security works. You have an impersonation NT token since you're impersonating a user, whereas you need a primary NT token to start processes.

    So you need to convert that to a primary token using DuplicateTokenEx, and you effectively need to be SYSTEM to do that.

    Once you have the primary token you can call CreateProcessAsUser.

    At this point you might have started the process as the user, but it doesn't have any credentials to reach other network properties like network shares or web services. You can enable constrained delegation on the service principal to any downstream services for that.

    An alternative, and probably safer and easier, option is instead to start a worker process as a low privilege user and tell that process to impersonate. See this answer for more information on that.