Security Briefs

Credentials and Delegation

Keith Brown

Code download available at: SecurityBriefs0509.exe (124 KB)
Browse the Code Online

I get loads of security questions from friends and former students, and recently I've gotten a number of questions about building secure data-driven Web sites for internal enterprise systems. I've decided to answer them here to hopefully save you some headaches in your own projects.

Q Can you tell me why Microsoft Internet Explorer some-times prompts me for credentials when I'm already logged on to my computer?

Q Can you tell me why Microsoft Internet Explorer some-times prompts me for credentials when I'm already logged on to my computer?

A I hear this question frequently. If you configure IIS to force integrated authentication, you'd expect that domain users would be able to use their single sign-on with your Web site, right? I remember being surprised by this behavior when I first saw it.

A I hear this question frequently. If you configure IIS to force integrated authentication, you'd expect that domain users would be able to use their single sign-on with your Web site, right? I remember being surprised by this behavior when I first saw it.

Here are the symptoms: the client is prompted for a password even though she's already logged on to the domain on her workstation. If she cancels the password prompt, she is denied access to the Web site. If she enters her domain credentials (user name and password), she is admitted without further prompts.

Here's what's going on under the covers. Whenever a client fires up a browser and points it to a Web site that requires Windows® integrated authentication, just like any initial HTTP request that Microsoft® Internet Explorer sends, the GET request goes up to the Web server without any sort of proof of who the client is. Internet Explorer has no idea which pages require authentication and which don't, so it errs on the side of protecting its client's anonymity, which is good! When the server receives this anonymous request, it cannot accept it, and thus returns an error message along with headers requesting client authentication.

This is where things get tricky. If Internet Explorer evaluates the URL to be part of the Trusted sites or Local intranet zones, the default security policy says that it should go ahead and complete the authentication handshake silently with the Web server. The client never sees all this handshaking going on: all she knows is that she is able to surf to the Web site without running into any problem.

On the other hand, if Internet Explorer can't place the URL in any other zone, it will fall into the Internet zone by default. The Internet zone is a catchall bucket, and has a rather restrictive security policy by default. Figure 1 shows the setting that's relevant here: in the Internet zone, Internet Explorer will staunchly protect your anonymity, even when a server requests client authentication. This is why the password prompt pops up—Internet Explorer is letting the user know that the Web server is going to find out who she is. If she cancels the prompt, Internet Explorer will not complete the authentication handshake and she will most likely be denied access to the Web site. If instead she types in her credentials, Internet Explorer will use them to complete the handshake, and if the server accepts the credentials, the user will likely be granted access.

Figure 1 Configuring the Local Intranet Zone

Figure 1** Configuring the Local Intranet Zone **

This still doesn't answer the question, though. Why would an internal site be considered part of the Internet zone and be subject to this more constrained policy? The answer is that the way zones are calculated, unless you're using a NETBIOS name when you surf to an internal Web site, the URL will most likely fall into the Internet zone bucket. The default heuristic for calculating if an URL is part of the Local intranet zone is to see if there are any dots in the name. Compare the following URLs:

https://www.microsoft.com/default.aspx
https://207.46.130.108/default.aspx
https://webserver/default.aspx

The first and second URLs will send you directly into the Internet zone by default. The third leads to the Local intranet zone, even though all three URLs may actually reference the same server. There are several ways around this. The first option is to ensure that all of your links to internal Web sites use NETBIOS names instead of DNS names. But this doesn't work well for companies that turn off support for NETBIOS. A much cleaner solution would be to ensure that all client machines in the domain are configured to recognize certain sites as internal.

You can experiment with this second option by opening the Internet Options control panel dialog and selecting the Security tab. From there, click the Local intranet zone and press the Sites button, followed by the Advanced button if you're running Windows XP. The dialog you arrive at allows you to add a list of Web sites that should be considered part of the Local intranet zone. You should consider working with your domain administrators to ensure that this list is populated appropriately for all domain clients via group policy. I've shown the location of this setting in group policy in Figure 2.

Figure 2 Configuring Internet Zone Group Policy

Figure 2** Configuring Internet Zone Group Policy **

It's pretty important to get this right. A user should never be prompted to type in her domain password after she's already logged in. Too much of that just paves the way for Trojan horses that steal passwords simply by asking for them.

Q How can my internal Web site use the client's single sign-on credentials to access SQL Server?

Q How can my internal Web site use the client's single sign-on credentials to access SQL Server?

A The answer depends a lot on whether SQL Server™ is installed on the same machine as the Web server or not. Let's keep things simple and imagine for a moment that SQL Server is right there on the Web server.

A The answer depends a lot on whether SQL Server™ is installed on the same machine as the Web server or not. Let's keep things simple and imagine for a moment that SQL Server is right there on the Web server.

In order to pass the client's credentials through to SQL Server, you should ensure that you are impersonating the client when you establish the connection. You can do this either with code or a configuration file. Figure 3 shows one approach for doing it in code from an .aspx page. This approach allows you to run your Web server under a fixed identity and gives you the choice of when you want to use your client's credentials (as opposed to your server's credentials) to access resources. As you can tell by the explicit cast to WindowsIdentity, I'm making some assumptions about how IIS will be authenticating clients. In this Web application I've simply turned off anonymous access in IIS and required Windows integrated authentication, which will force all clients to authenticate right at the front door. This may be a good strategy for internal-facing enterprise Web applications because it leverages the client's single sign on, assuming you have dealt with the tricky little issue I mentioned in the answer to the first question.

Figure 3 Programmatic Impersonation

<%@ page language='c#'%>
<%@ import namespace='System.Data'%>
<%@ import namespace='System.Data.SqlClient'%>
<%@ import namespace='System.Security.Principal'%>

<%
string connStr = "integrated security=sspi;database=pubs";
using (SqlConnection conn = new SqlConnection(connStr)) {
    WindowsIdentity id = (WindowsIdentity)User.Identity;
    WindowsImpersonationContext wic = id.Impersonate();
    try {
        conn.Open(); // open while impersonating the client
    }
    finally {
        wic.Undo(); // stop impersonating
    }

    string sql = "select au_fname, au_lname from authors";
    SqlCommand cmd = new SqlCommand(sql, conn);
    using (IDataReader r = cmd.ExecuteReader()) {
        while (r.Read()) {
            Response.Output.WriteLine("{0} {1}<br>", r[0], r[1]);
        }
    }
}
%>

If you would rather impersonate all the time, you can simply configure your Web application to impersonate the client by default:

<configuration>
    <system.web>
        <identity impersonate='true'/>
    </system.web>
</configuration>

Now all connections to SQL Server from your .aspx pages will use the client's credentials, but so will any requests for local resources such as files. This may not be what you want. If you're impersonating by default and you need to temporarily stop impersonating to open a file or other resource using your Web server's identity, you can do that too:

// stop impersonating
WindowsImpersonationContext wic =
    WindowsIdentity.Impersonate(IntPtr.Zero);
try {
    // using the server's credentials, not the client's
}
finally {
    wic.Undo(); // resume impersonating
}

Note that in both cases where I wrote code to change security contexts, I was careful to use a try/finally block to ensure that no matter what happened, I was able to get back to my previous security context. This is vital. You don't want an exception thrown at the wrong time to cause your application to run code under an unexpected security context.

Q What should I do if SQL Server isn't on the same machine

Q What should I do if SQL Server isn't on the same machine

A I see I'm not going to get away with explaining only the simple case, so imagine that your connection string specifies a remote server. Now the Web server on machine B will impersonate a remote client who is logged onto machine A, and will use the client's credentials to access SQL Server on machine C. This requires more than impersonation: it requires delegation. When the client on machine A authenticates with the Web server on machine B, she proves that she knows a secret associated with her account (typically a password). But she doesn't tell machine B what the password is. In normal circumstances, machine B has no way of proving to machine C that it really does have a valid logon for the client, but C needs proof.

A I see I'm not going to get away with explaining only the simple case, so imagine that your connection string specifies a remote server. Now the Web server on machine B will impersonate a remote client who is logged onto machine A, and will use the client's credentials to access SQL Server on machine C. This requires more than impersonation: it requires delegation. When the client on machine A authenticates with the Web server on machine B, she proves that she knows a secret associated with her account (typically a password). But she doesn't tell machine B what the password is. In normal circumstances, machine B has no way of proving to machine C that it really does have a valid logon for the client, but C needs proof.

You may have tried this before. If an .aspx page impersonates its client and then uses integrated security to open a SQL Server connection to a remote database using the following connection string

integrated security=sspi;database=pubs;server=C

the result is a rather cryptic exception from SqlConnection.Open:

Login failed for user '(null)'. Reason: Not associated with a
trusted SQL Server connection.

I highly recommend auditing success as well as failure of logon events during these types of tests. With auditing turned on, in this case you'll see that somebody named ANONYMOUS LOGON has logged onto machine C. What's happening is that because the Web server doesn't have network credentials for the client, when it impersonates the client and tries to open a connection to the database, instead of authenticating, it ends up establishing what is called a null session instead, thus the "null" in the error message from SQL Server. And the null session is denied access by default, which is a good thing.

The first step to making this work is to go into Active Directory® and grant the Web server the right to delegate client credentials. You need to tell Active Directory that you have designed machine B to pass through client credentials, and that you trust B enough to assume that it won't abuse this privilege by using delegated client credentials to access other servers on the network other than the one it's designed to access, which in this case is the database server C. You might even be able to eliminate this particular assumption, as I'll show you later.

By default, IIS 6.0 worker processes run under the built-in Network Service logon, which uses the computer's domain credentials. So you must visit the computer account for B in Active Directory and designate that computer account as "Trusted for delegation." The user interface for doing this differs a bit between Windows 2000 and Windows Server™ 2003. The former simply has a checkbox on the first property page you see when you bring up the computer account properties. The latter actually has an entire property page dedicated to delegation, and since it provides a superset of the functionality in Windows 2000, that's where I'll do my demos. Figure 4 shows the Windows 2000 compatible delegation setting (I've elided the lower portion of this dialog for now, but I'll come back to it later). This setting is equivalent to checking the "Trusted for delegation" box for a computer in a Windows 2000 domain.

Figure 4 Windows 2000 Delegation

Figure 4** Windows 2000 Delegation **

With these new settings in place, the client should log off and back on again to clear her Kerberos ticket cache, and the next time she connects, her credentials should flow to SQL Server as desired. Of course things aren't usually this easy, as you will see next.

Q I've done all that you said, but delegation still doesn't work. What's wrong?

Q I've done all that you said, but delegation still doesn't work. What's wrong?

A First of all, you need to realize that delegation of credentials in Windows is a feature specific to Kerberos. But Windows doesn't always use Kerberos to authenticate clients, even if those clients are using domain accounts. What's really being used under the covers is a meta-protocol whose friendly name is "Negotiate." The two protocols negotiated are Kerberos and Windows NT LAN Manager (NTLM), in that order, and if you negotiate down to NTLM, delegation is not going to work. For example, if either the Web server or SQL Server is running under a local account (such as the default ASPNET account on Windows 2000) or the built-in Local Service account, Kerberos cannot be used, so NTLM will be negotiated, and delegation will not work. In order to have a chance at negotiating Kerberos, both client and server must be using domain accounts to authenticate.

A First of all, you need to realize that delegation of credentials in Windows is a feature specific to Kerberos. But Windows doesn't always use Kerberos to authenticate clients, even if those clients are using domain accounts. What's really being used under the covers is a meta-protocol whose friendly name is "Negotiate." The two protocols negotiated are Kerberos and Windows NT LAN Manager (NTLM), in that order, and if you negotiate down to NTLM, delegation is not going to work. For example, if either the Web server or SQL Server is running under a local account (such as the default ASPNET account on Windows 2000) or the built-in Local Service account, Kerberos cannot be used, so NTLM will be negotiated, and delegation will not work. In order to have a chance at negotiating Kerberos, both client and server must be using domain accounts to authenticate.

A much more subtle issue is that Kerberos requires something called a Service Principal Name (SPN) in order to function. In order to get a Kerberos ticket for a server, the client must have a way of identifying the server's domain account. Physically this is because the ticket she gets will be encrypted using a master key that is derived from the server's password, and when she sends it to the server, the server must be able to decrypt the ticket. Logically this helps assure the client that the server is not being spoofed by a bad guy, because the bad guy doesn't know the server's password and therefore cannot use the ticket in a mutual authentication Kerberos handshake.

This all sounds really technical, but an SPN is really quite a simple idea: it's just a way to uniquely identify a service running on a particular machine and listening on a particular port. For example, if the system administrator is planning to run a default instance of SQL Server on a machine whose DNS name is data.acme.com, he would create a user account (let's call it ACME\DataAccount) in the domain, and configure SQL Server to use that account. He would then add the following SPN to the ACME\DataAccount user account:

MSSQLSvc/data.acme.com:1433

This tells Active Directory that there is an authorized instance of a service of class "MSSQLSvc" listening on port 1433 on data.acme.com, and that it is supposed to be running as a user called DataAccount in the ACME domain. If anyone needs to get a ticket for that service, the ticket should be encrypted with the master key for ACME\DataAccount.

Now if SQL Server runs with high privilege, say as SYSTEM, it registers this SPN automatically each time it starts. But running SQL Server as SYSTEM is really a bad idea from a security standpoint. For example, it's "game over" if an attacker is able to use a SQL injection vulnerability in your application to execute the xp_cmdshell extended stored proc in a SQL Server that's running as SYSTEM. Besides reviewing your code for holes like this, you should prefer to run SQL Server under a normal user account to provide defense in depth.

Running SQL Server under a least privileged user account means you'll need to register an SPN manually. It's not hard, but it's one of those things that can trip you up on your way to getting delegation working if you aren't aware that you need to accomplish it.

There is a tool called SETSPN.EXE that ships with the various server flavors of Windows. It's part of the support tools installation that you'll find in a subdirectory on the CD. Here's how to use it to see what SPNs are associated with a particular user account:

setspn –L ACME\DataAccount

The following is the command for adding the SPN that I mentioned for SQL Server:

setspn –A MSSQLSvc/data.acme.com:1433 ACME\DataAccount

For delegation to work, you need Kerberos to be negotiated between the client and the Web server, and between the Web server and the database. This means that both the Web server and the database must have the appropriate SPNs. Internet Explorer asks for tickets using the HTTP service class, so if you're running your Web application on a machine called Web under a custom domain account called ACME\WebAccount, you'll need to register an SPN for that account, like so:

setspn –A http/web.acme.com ACME\WebAccount

Q I am having some trouble when attempting to diagnose delegation problems. Do you have any helpful tips?

Q I am having some trouble when attempting to diagnose delegation problems. Do you have any helpful tips?

A Absolutely. The first thing you should do if you suspect that incorrect security configuration is causing legitimate users to have trouble accessing your system is to enable auditing of logon events on the machines hosting the database and Web server. To do this, drill into the local security policies of those machines under Security Settings | Local Policies | Audit Policy | Audit logon events. If the UI won't allow you to change these settings, you'll need to talk to the domain administrator because they are being controlled by group policy. Turn on auditing for both success and failure, then run your scenario again.

A Absolutely. The first thing you should do if you suspect that incorrect security configuration is causing legitimate users to have trouble accessing your system is to enable auditing of logon events on the machines hosting the database and Web server. To do this, drill into the local security policies of those machines under Security Settings | Local Policies | Audit Policy | Audit logon events. If the UI won't allow you to change these settings, you'll need to talk to the domain administrator because they are being controlled by group policy. Turn on auditing for both success and failure, then run your scenario again.

Follow the flow of the client's request by looking at the Web server's log first (use the event viewer and look at the security log). If all went well, you should see a successful logon event with your client's name on it. If you open up the log entry, you should see that the logon was established through Kerberos. If you see NTLM, you know you've negotiated down, and should review the tips I've presented here to see if there's something you missed. On the database machine, you should also see a login for the same user, via Kerberos. What will often happen when you're first getting delegation set up is that you'll instead see a logon event from a user called ANONYMOUS LOGON. This indicates a null session, and this will happen if the Web server authenticates with the back end while impersonating a client who used NTLM to connect. Remember, NTLM doesn't support delegation.

Another tool that I've found useful for diagnosing Kerberos problems is called KERBTRAY.EXE, which ships with the resource kit for Windows Server operating systems. You can download the tools for the Windows Server 2003 resource kit from Windows Server 2003 Resource Kit Tools. You shouldn't be without these tools.

When run, KERBTRAY.EXE adds a tray icon from which you can both list all the Kerberos tickets in the interactive user's logon session and purge those tickets. Purging tickets is convenient if you want to retry a scenario without having to log off and log back on. For example, when you change delegation settings in Active Directory, it's a really good idea to purge your client's ticket cache before trying to connect again.

Listing tickets can be very helpful, because you can see if the Web server's ticket has the ok-as-delegate flag set (it won't if you're using constrained delegation, but more on that in a moment). You can also see if the client's Ticket-Granting Ticket (TGT) has the forwardable flag set. If it does not, the client's account likely has been marked "sensitive and cannot be delegated". Unfortunately, KERBTRAY.EXE can't be used to determine which tickets the Web server has when it's impersonating you. But I've written a helpful sample (included in the download) that allows you to list the tickets and echo them back via an .aspx page (use this while impersonating the client). This will help you determine whether the client's credentials have indeed been forwarded to the middle tier in a delegation scenario.

Q Do you consider it to be dangerous to trust a Web server to delegate credentials?

Q Do you consider it to be dangerous to trust a Web server to delegate credentials?

A That's a very astute observation! (By now, you're probably starting to wonder who is writing these questions aren't you?)

A That's a very astute observation! (By now, you're probably starting to wonder who is writing these questions aren't you?)

Imagine what happens if the Web server is somehow compromised, and an attacker can run code of his choosing in the Web application's process. The attacker simply has to wait for clients to come along, at which point he can impersonate them and access any machine on the network using their network credentials. Think how awful it would be if a domain administrator from an entirely different domain (perhaps the human resources domain) happened to log onto your Web application during an attack like this. This is one reason why all high-privileged accounts such as domain administrators should be marked with the "sensitive and cannot be delegated" flag in Active Directory. I sure wish that flag could be set for a group (like Domain Admins).

If you have a Windows Server 2003 domain and your Web server is also running on Windows Server 2003, you don't need to trust the Web server nearly this much in order to support delegation. I mentioned earlier that there is a whole new delegation infrastructure in Windows Server 2003, and Figure 5 shows how you can make use of it. Here, instead of choosing the Windows 2000-compatible mode of delegation, I've chosen to use a feature called constrained delegation. This allows me to provide a list of back-end servers to which the Web server should be allowed to delegate credentials. This is actually a list of service principal names, so the one item I've got in the list should look familiar. That's the SPN for SQL Server listening on port 1433 on the machine data.acme.com.

Figure 5 Constrained Delegation in Windows

Figure 5** Constrained Delegation in Windows **

With constrained delegation, domain controllers will refuse to issue delegated tickets for any clients, unless the SPN being requested is in the allowed-to-delegate-to list that I've shown in Figure 5. This allows the Web server to use its clients' credentials to talk to SQL Server, but no other servers on the network. This is a very good thing, and if you've upgraded to Windows Server 2003, you should absolutely be using it.

If you're using KERBTRAY.EXE to view the client's tickets, note that under constrained delegation, the Web server's ticket won't be marked ok-as-delegate. This is because constrained delegation works very differently from normal Kerberos TGT forwarding, which is what happens when you use the Windows 2000-compatible delegation option. Under constrained delegation, the client does not forward its TGT to the server, because that would allow the server to use those credentials anywhere on the network. Instead, the client just performs a normal Kerberos handshake with the Web server, and the Web server uses a special extension to Kerberos called S4U2Proxy to obtain a ticket to the back end on the client's behalf. The domain controller will only issue these proxy tickets for SPNs on the Web server's allowed-to-delegate-to list, shown in Figure 5. For more details on the S4U Kerberos extensions, see my April 2003 MSDN®Magazine column at Security Briefs: Exploring S4U Kerberos Extensions in Windows Server 2003.

Just remember, building a distributed, multitier system means making tradeoffs. In this case, the tradeoff is between connection pooling and delegation. While connection pooling might eke a bit more throughput out of your system, delegation means your database doesn't have to trust that Web server so much, and that can be a really good thing if your Web server is ever compromised. Security is never free. But before making a decision one way or another, do some threat modeling and performance profiling, and decide which option is right for you.

Send your questions or comments for Keith to  briefs@microsoft.com.

Keith Brown is a co-founder of Pluralsight, specializing in developing and delivering high-quality training for software developers. Keith's most recent book, The .NET Developer's Guide to Windows Security, is available at www.pluralsight.com/keith/book. Keith also keeps a Web log and contact information at www.pluralsight.com/keith.