Show Changes Show Changes
Edit Edit
Print Print
Recent Changes Recent Changes
Subscriptions Subscriptions
Lost and Found Lost and Found
Find References Find References
Rename Rename
Search

History

12/11/2007 12:56:32 PM
PSWEB-keith
1/13/2006 8:26:42 AM
-63.227.2.187
1/13/2006 8:26:26 AM
-63.227.2.187
1/13/2006 8:25:05 AM
-63.227.2.187
List all versions List all versions
How To Get A Token For A User
.

Getting a token (WhatIsAToken) for a user is tremendously easy if you happen to be running on a Windows Server 2003 machine in a native Windows Server 2003 domain. You can simply construct a new WindowsIdentity, passing in the user principal name (UPN) for the account, which for ACME\Alice is typically something like alice@acme.com.1 Here's an example:

 using System;
 using System.Security.Principal;


 class IsUserAnAdmin {
   static void Main(string[] args) {
     if (1 != args.Length) {
       Console.WriteLine("Usage: IsUserAnAdmin userPrincipalName");
       return;
     }
     string upn = args[0];
     // here's the magic constructor
     WindowsIdentity id = new WindowsIdentity(upn);
     WindowsPrincipal p = new WindowsPrincipal(id);
     if (p.IsInRole(WindowsBuiltInRole.Administrator)) {
       Console.WriteLine("{0} IS an admin of this box", upn);
     }
     else {
       Console.WriteLine("{0} is NOT an admin of this box", upn);
     }
   }
 }

This feature is new to Windows Server 2003. It only works with domain accounts and has some restrictions that I talk about in WhatIsProtocolTransition.

Calling LogonUser

If you're trying to do the same thing with a local account, or a domain account on a Windows 2000 or Windows XP box, you'll need the user's password to get a token for it. Where are you going to get the user's password, though? If you need to do this sort of thing, prompt the user for a password (HowToPromptForAPassword). If the user won't be present, then you'll need to store the password someplace where the machine can read it. This is really bad news because, even if you do this as carefully as possible (HowToStoreSecretsOnAMachine), root compromise of the machine eventually leads to compromise of these secrets, and you can't prevent this. This should be a major consideration in your threat model (WhatIsThreatModeling)!

Now that I've warned you adequately, there is a Win32 API that you can call to authenticate a name and password: LogonUser. This API takes the user's name, authority (domain name for a domain account or machine name for a local account), and password; verifies this information with the authority; and establishes a logon session (WhatIsALogonSession). It returns a token that references the new session which you can use to impersonate the user, find out what groups she's in, and so on.

But there's a flaw in the Windows 2000 implementation of LogonUser. This function is implemented in terms of a lower-level (and much more powerful) function called LsaLogonUser, which takes about a hundred thousand parameters (I exaggerate, but only just a little). Anyway, this lower-level function is so powerful that only users with SeTcbPrivilege are allowed to use it, which basically means you need to run as SYSTEM to call it. Why? Because you’re allowed to inject arbitrary SIDs into the resulting token. Want to pretend that a user is really an administrator of the machine? This function allows you to inject the BUILTIN\Administrators SID into the resulting token, so clearly we don't want nonprivileged users calling it! But here's the thing: Because the very useful LogonUser function calls LsaLogonUser, it also requires this same privilege, even though it doesn't allow you to pass in those extra SIDs. Programs that need to call this function on Windows 2000 often end up being configured to run as SYSTEM, even though they might not normally need any special level of privilege. This is bad!

This flaw has been remedied in modern versions of Windows such as Windows XP and Windows Server 2003, so let's proceed for the moment assuming that we can simply call LogonUser to get a token for a user without having any special privileges. Later in this item I'll show a workaround for those of you running Windows 2000. In Figure 26.1 I provide a C# program that calls LogonUser via P/Invoke.

 using System;
 using System.Security.Principal;
 using System.Runtime.InteropServices;
 using Pluralsight.WindowsSecurityUtilities;


 class LogonAlice {
   static void Main() {
     WindowsSecurityUtilities.Initialize();
     // from our helper library
     string pwd = PasswordPrompt.GetPasswordFromCmdLine();
     IntPtr token;
     bool result = LogonUser(
         "Alice", "ACME", // "ACME\Alice"
         pwd,
         LogonTypes.Batch,
         LogonProviders.Default,
         out token);
     if (result) {
       // from our helper library
       WindowsIdentity id = Token.CreateWindowsIdentity(token);
       CloseHandle(token);
       WindowsPrincipal p = new WindowsPrincipal(id);


       // use IPrincipal/IIdentity to query the token
       Console.WriteLine("{0} is a User: {1}", id.Name,
         p.IsInRole(WindowsBuiltInRole.User));
       Console.WriteLine("{0} is an Admin: {1}", id.Name,
         p.IsInRole(WindowsBuiltInRole.Administrator));
     }
     else {
       Console.WriteLine("LogonUser failed: {0}",
         Marshal.GetLastWin32Error());
     }
     WindowsSecurityUtilities.Terminate();
   }
   [DllImport("advapi32.dll", SetLastError=true)]
   static extern bool LogonUser(
     string principal,
     string authority,
     string password,
     LogonTypes logonType,
     LogonProviders logonProvider,
     out IntPtr token);
   [DllImport("kernel32.dll", SetLastError=true)]
   static extern bool CloseHandle(IntPtr handle);
   enum LogonTypes : uint {
     Interactive = 2,
     Network,
     Batch,
     Service,
     NetworkCleartext = 8,
     NewCredentials
   }
   enum LogonProviders : uint {
     Default = 0, // default for platform (use this!)
     WinNT35,     // sends smoke signals to authority
     WinNT40,     // uses NTLM
     WinNT50      // negotiates Kerb or NTLM
   }
 }

Figure 26.1 Calling LogonUser from C#

When calling LogonUser, you must choose the type of logon you want the user to establish. There are four types, and each requires the user being logged on to hold a corresponding user right. You may recognize some of these user logon rights from poking around in security policy under "User Rights Assignment," the same place where privileges are granted (WhatIsAPrivilege).

The easiest by far to establish is the network logon. Everyone is granted this right by default everywhere on the network. Denying it to someone is tantamount to saying, "I will not accept your credentials on this machine: get lost!" I would recommend sticking to a network logon if at all possible. It's also much faster than establishing an interactive logon when working with domain accounts. The other nice thing about the network logon is that you get to choose whether the resulting logon session will cache the client's master key (WhatIsKerberos) so that you can contact remote servers while impersonating the user (WhatIsImpersonation). If you need network credentials for the user, choose the LogonTypes.NetworkCleartext logon option. Otherwise, by all means choose the LogonTypes.Network option so that the client's key won’t be cached. This is an example of applying the principal of least privilege (WhatIsThePrincipleOfLeastPrivilege).

The interactive logon is what winlogon.exe uses when you log on to a machine interactively. The only time you want to use it is when you want to run an interactive process as the user, but there's a much easier way of doing that than calling LogonUser. See HowToRunAProgramAsAnotherUser for more details. Finally, batch and service logons are used by COM servers and Windows services, respectively. Unless you're writing very specialized code for launching background processes, you should avoid these as well. The network logon is your friend!

The SSPI Workaround

Remember that flaw in LogonUser that essentially requires you to run as SYSTEM to call it on Windows 2000? Well, there's a workaround that's come in handy for a number of people I've helped, but it's limited in that the resulting logon session will not have network credentials for the user. Nevertheless, it might just get you out of a jam, so here we go.

The trick is to perform an SSPI handshake (WhatIsSSPI) with yourself, playing the role of both client and server. And the cool thing is, with version 2.0 of the .NET Framework it's trivial to implement. I show how to use the NegotiateStream class in HowToAddCIAToASocketBasedApp to build a client and server that authenticate across the network. Well, to authenticate a user given her name and password, you just do the same thing but without the network. Attach two instances of NegotiateStream together, one for the client and one for the server, and perform both sides of the handshake. The code is shown in Figure 26.2.

 using System;
 using System.Net;
 using System.Net.Sockets;
 using System.Net.Security;
 using System.Security.Principal;


 /* NOTE: This has been updated from the original version in the book, which didn't
          function properly under the RTM bits - hope this helps! */
 class Program {
     static void Main(string[] args) {
         WindowsPrincipal alice = SSPIHelper.LogonUser("alice", "gromit", "P@ssw0rd");
         Console.WriteLine("Logged on {0}", alice.Identity.Name);
     }
 }


 public class SSPIHelper {
     public static WindowsPrincipal LogonUser(string userName, string domain, string password) {
         // need a full duplex stream - loopback is easiest way to get that
         TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 0);
         tcpListener.Start();


         WindowsIdentity id = null;
         tcpListener.BeginAcceptTcpClient(delegate(IAsyncResult asyncResult) {
             using (NegotiateStream serverSide = new NegotiateStream(
                      tcpListener.EndAcceptTcpClient(asyncResult).GetStream())) {
                 serverSide.AuthenticateAsServer(CredentialCache.DefaultNetworkCredentials,
                      ProtectionLevel.None, TokenImpersonationLevel.Impersonation);
                 id = (WindowsIdentity)serverSide.RemoteIdentity;
             }
         }, null);


         using (NegotiateStream clientSide = new NegotiateStream(new TcpClient("localhost",
                      ((IPEndPoint)tcpListener.LocalEndpoint).Port).GetStream())) {
             clientSide.AuthenticateAsClient(new NetworkCredential(userName, password, domain),
                      "", ProtectionLevel.None, TokenImpersonationLevel.Impersonation);
         }
         return new WindowsPrincipal(id);
     }
 }

Figure 26.2 Using SSPI to establish a network logon

The service principal name (SPN, introduced in WhatIsAServicePrincipalNameSPN) is easy to calculate. Because my process is acting as the server, I can just query my token to find out which user is running the program (and thus acting as the server). This is the reason I call WindowsIdentity.GetCurrent. I use this name as the SPN.

Because you don't need to establish a secure channel (note that both instances of NegotiateStream are discarded as soon as the handshake is complete), choose a security level of SecurityLevel.None In this way the only thing that SSPI will do is authenticate and get us a logon session and a token for the user.

On success, you'll end up with a network logon for the user with no network credentials. Thus you can use the resulting token on the local machine, but don't try to impersonate using this token in order to authenticate with remote servers: It won't work. You'll end up establishing a null session with the remote server instead (WhatIsANullSession). If you need those network credentials, you'll have to call LogonUser as shown above.

1 The UPN is typically an e-mail address, and it's stored as part of the user's account in Active Directory. Look on the Account tab for the user account in Active Directory, Users and Computers, and it's the first thing you'll see: "User logon name." If you're logged in under a domain account, you can use whoami /upn to discover your own UPN as well.

PortedBy AnonymousDonor

PluralsightTraining

Keith's first book-in-a-wiki. If you would like to read the book online or order a physical copy to throw at annoying coworkers, surf to the HomePage. Please note that due to overwhelming wikispam, this particular wiki is no longer editable.

About FlexWiki.

Recent Topics