App Lockdown Supplement

The original draft of my latest security article for MSDN Magazine, App Lockdown, included sections covering web security. They were ultimately removed as they overlapped slightly with other articles already featured by MSDN Magazine. They are however part of the story I wanted to tell and so I include those sections here.

If you enjoyed my article, I encourage you to read these sections on web security to complete the story.

Protecting Web Clients

Classic web technologies have made it very easy to build insecure web-based systems. Writing secure web services and clients takes some works because the web was founded on the idea of openness rather than privacy and security. The original web authentication protocols, such as Basic authentication, are primitive compared to the network authentications protocols used by Windows. In this section I will focus on the security model of web clients and what you need to do to secure your web service clients.

Unlike the previous types of clients we have talked about, web clients really do not have much say over the quality of authentication and privacy. The client needs to be satisfied with the authentication scheme provided by the server as well as the level of privacy offered for protecting data. With native Windows authentication the client is typically in charge of negotiating a satisfactory form of authentication, data integrity and privacy.

Here is an example of a simple anonymous web request using the .NET Framework:

Uri uri = new Uri("http://server/application");
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
 
using (WebResponse response = request.GetResponse())
{
    // TODO: read response
}

Assuming the server supports anonymous access, the request should succeed. The simplest way to provide a set of credentials to authenticate the client is to create a NetworkCredential object and assign it to HttpWebRequest’s Credentials property. Using this approach, however, is not very safe since you do not know how those credentials will be used. If the server requested Basic authentication then the credentials will be sent over the wire which is clearly not safe. A better approach is to use the CredentialCache class to have a say in negotiating the authentication protocol. Here is a more interesting example:

Uri uri = new Uri("https://server/application");
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
 
NetworkCredential credentials = new NetworkCredential("principal@authority",
                                                      "password");
 
CredentialCache cache = new CredentialCache();
 
cache.Add(new Uri("https://server/"),
          "Basic",
          credentials);
 
cache.Add(new Uri("http://server/"),
          "Negotiate",
          credentials);
 
request.Credentials = cache;
 
using (WebResponse response = request.GetResponse())
{
    // TODO: read response
}

In this example the web request will use Basic authentication only if SSL is also used, otherwise it will use Windows authentication. The credentials and authentication scheme to use is determined by finding the closest match to the URI prefix in the cache. If the actual URI begins with the https scheme it indicates that SSL will be used. If you simply want to use the client’s current security context then set the HttpWebRequest’s Credentials property to CredentialCache.DefaultCredentials. Assuming the server supports Windows authentication, a network logon session will be created for you on the server. Keep in mind that Basic authentication can be risky even if you employ SSL. If the server is somehow compromised, a bag guy can easily get the client’s cleartext credentials and do unspeakable things masquerading as the client. Windows authentication provides a better solution by proving the client’s identity without ever sending the credentials over the wire.

Despite the benefits of Windows authentication over HTTP, it is not a great way to make friends on the Internet. Few web clients support Windows authentication and few web servers allow it. Even if they do, you still have to deal will the multitude of firewalls that are specifically designed to block anything but simple HTTP requests over well-known ports. One of the benefits of using SSL is that it can provide client authentication. Although not practical for large-scale web applications like amazon.com where SSL is only used for server authentication and privacy, SSL client authentication provides a very portable and secure form of authentication for web clients. Authentication is achieved through the use of client certificates. In a typical SSL handshake, the server proves its identity by presenting the client with its certificate. To authenticate the client with a client certificate, the client simply needs to provide the server with its certificate. This is clearly an oversimplification, but you get the point.

To use a certificate, the client needs to be issued a certificate by a certificate authority. Since the certificate has an associated private key, it needs to be kept safe. This is handled by a certificate store which you can access using the Cryptography API or though Internet Explorer. Here is an example of using a client certificate:

Uri uri = new Uri("https://server/application");
X509Certificate certificate = X509Certificate.CreateFromCertFile(@"C:\client.cer");
 
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
request.ClientCertificates.Add(certificate);
 
using (WebResponse response = request.GetResponse())
{
    // TODO: read response
}

As you can see, using SSL and client certificates for authentication is quite simple as long as you have the infrastructure set up to support it. Due to the hostile nature of the Internet, it is very likely that a bag guy will attempt to redirect the client to a different server under his control. Therefore proving the identity of the server is important. Server authentication is also managed by a certificate when using SSL. The server presents the client with a certificate that the client can then use to validate the server’s identity. Of course for this to make any sense, a central authority is required. In Windows networking this is achieved by using Kerberos and a Windows domain controller. The web equivalent is through the use of certificate authorities and a chain of trust. The client needs to trust a certificate authority that directly or indirectly issued the web server with its certificate. In this way the client can validate the integrity of the server’s certificate. This is largely an administrative task to configure servers and clients with mutually acceptable certificate authorities. But sometimes all you want is to use SSL for either client authentication or privacy and you do not care about server authentication. This may be acceptable for connecting to a web server on a trusted network or simply in development and testing of your application to avoid the administrative overhead of certificate management. The challenge is that the default behavior for web clients is to validate the server certificate. To provide custom certificate validation a web client can provide an implementation of the ICertificatePolicy interface and assign it to the static ServicePointManager.CertificatePolicy property. Future web requests that use SSL will then call the ICertificatePolicy.CheckValidationResult method to determine whether or not the certificate should be honored. Here is a simple example that will accept any server certificate:

class CustomCertificatePolicy : ICertificatePolicy
{
    public bool CheckValidationResult(ServicePoint servicePoint,
                                      X509Certificate certificate,
                                      WebRequest request,
                                      int errorCode)
    {
        return true;
    }
}

For more control over validation you can query the certificate as well as check the error code to determine the reason for validation failure. The error codes that you can expect are listed in the documentation for the CERT_CHAIN_POLICY_STATUS structure in the Platform SDK.

So far we have only discussed web clients in the context of simple HTTP requests. Web programming has come a long way since the days of simple HTTP GET request and response messages. As a web client developer you need to understand what is involved in securing you clients in the new programmable web. The following is a classic web service client proxy using the .NET Framework:

[WebServiceBinding(Name="SampleServiceSoap",
                   Namespace=SampleService.WebServiceNamespace)]
class SampleService : SoapHttpClientProtocol
{
    public const string WebServiceNamespace = "http://schemas.kennyandkarin.com/sample/";
 
    [SoapDocumentMethod(WebServiceNamespace + "EchoUserName")]
    public string EchoUserName()
    {
        object[] results = Invoke("EchoUserName", new object[0]);
        return (string) results[0];
    }
}

To connect to a Web service using this proxy class, simply create an instance of the SampleService class, set the Url property to the address of the web service endpoint and call the EchoUserName method. This method will block until the SOAP response message is received and return the string contained in the message. Authentication works in the same way as I described for simple HTTP requests. You can use a CredentialCache object to provide a set of credentials to use and set the Credentials property of the SampleService class. Internally the credentials will be passed to the underlying HttpWebRequest object that will actually make the SOAP request over HTTP. That is Web service client programming with the .NET Framework in a nutshell. To take advantage of some of the more modern web security standards you need to turn to Web Services Enhancements 2.0 (WSE) which is an extension to the .NET Framework that provides an implementation of WS-Security. To start using WSE in your client application you need to add a reference to Microsoft.Web.Services2 to your assembly. To take advantage of it for the SampleService class described above, simply change the base class from SoapHttpClientProtocol to the WebServicesClientProtocol class from the Microsoft.Web.Services2 namespace. That’s it! You’ve just written a WSE client. It may not look like anything has changed and indeed the SOAP envelope body will be exactly the same, but if you happen to have a SOAP trace going, you will have noticed that there is now a SOAP envelope header with all kinds of interesting information. If the Web service you are connecting to knows nothing about WSE, it will just be ignored unless the headers are attributed with mustUnderstand=”1”.

The client can provide credentials in the form of security tokens in the Security SOAP header. Here is an example using a WS-Security UsernameToken:

SampleService service = new SampleService();
service.Url = "http://server/Service.asmx";
 
UsernameToken token = new UsernameToken("principal@authority",
                                        "password",
                                        PasswordOption.SendPlainText);

Security securityHeader = service.RequestSoapContext.Security;
securityHeader.Tokens.Add(token);
       
string userName = service.EchoUserName();

Other security token types are available to support certificate authentication as well as Windows authentication using Kerberos security tokens. The UsernameToken is similar to Basic authentication in HTTP. The credentials are sent across the wire in the clear and it even results in the same type of logon session on the server namely a network logon session with network credentials. While experimenting or developing with Web services I often find it useful to use an HTTP trace tool such as the MSSoapT tool from the Microsoft SOAP Toolkit to capture the request and response messages. To do this you need to direct your client code to a different port that the trace tool is listening on. The trace tool will forward the request on to the final destination after capturing the SOAP envelope. This can be a problem for WSE since it has addressing and security capabilities that validate the destination address to ensure that it matches up to what was expected. Of course since WSE supports addressing, it is possible to indicate what the final destination of the message is so that routing is possible. To enable this, simply create a Uri object with the actual address of the Web service and set the Destination property of the AddressingHeaders object for the request:

UriBuilder uri = new UriBuilder("http://server:8080/Service.asmx");
service.Url = uri.ToString();
 
uri.Port = 80;
AddressingHeaders addressingHeaders = service.RequestSoapContext.Addressing;
addressingHeaders.Destination = new EndpointReference(uri.Uri);

Web Services Enhancements 2.0 provides a wealth of powerful functionality to provide fine grained control over the security aspects of your web service programming from authentication to integrity and privacy. For in-depth information on WSE, visit the Web Services Developer Center on MSDN.

Defending Web Servers

There are a number of different options for authenticating web clients. Internet Information Services (IIS) makes it relatively easy to use Windows user accounts to authenticate web clients. Certificates can also be used to authenticate clients when SSL is in use and this is just what IIS provides. Applications can elect to enable anonymous access in IIS and implement their own authentication scheme. One great example of this is ASP.NET Forms authentication. Of course this is only suitable for a web application that doubles as the presentation layer. For Web services you can employ WS-Security to provide more fine-grained control over security capabilities.

To begin to understand how web server security works, it helps to have an understanding of the different security contexts presents at any given time. In Protecting COM Clients I mentioned the notion of an effective token or security context. The effective token is the thread token if one exists, otherwise it is the process token. The .NET Framework exposes the concept of an effective token through the GetCurrent static method of the WindowsIdentity class. The resulting WindowsIdentity object wraps the effective token and provides an elegant interface for querying it. Calling the WindowsIdentity.GetCurrent method from within a web method can tell you a lot about how IIS and ASP.NET manage security contexts. Consider the following simple web method:

[WebMethod]
public string EchoUserName()
{
    WindowsIdentity identity = WindowsIdentity.GetCurrent();
    return identity.Name;
}

What will this return? Well it depends on many things. Let us consider a few options. If the impersonate attribute (system.web/identity/@impersonate) in the web application’s web.config file is set to false it will return the name of the process identity, which defaults to the Network Service account. If the impersonate attribute is set to true and the web client is connecting anonymously it will return the name of the account representing anonymous clients, which is typically SERVER\IUSR_SERVER where SERVER is the host name of the web server. If the web application does not accept anonymous connections it will return the name of the account represented by whatever logon session was established for the client. Getting a feel for how ASP.NET, IIS and Windows combine to provide security for you web applications will take you a long way in understanding web server security in general.

Using Windows user accounts for managing authentication and authorization is extremely convenient and can save a lot of work in development and maintenance. But using Windows user accounts does not always make sense. There are two parts to the problem. The first is that there is a need for a more universally acceptable form of identification. A popular answer to this is X.509 certificates. The second problem is that the mechanics of authentication at the web server and HTTP levels are just not flexible enough to meet the needs of Web service-based applications.

Due to the universal support for SSL in web clients and servers, using SSL to provide data integrity, privacy and authentication is quite simple. The biggest obstacle is issuing certificates to servers and clients that can then be trusted by all through some direct or indirect certificate authority. If the client sent a certificate along with its request, you can retrieve information about the certificate using the HttpClientCertificate object returned by the HttpRequest.ClientCertificate property.

In Protecting Web Clients, I briefly discussed how you can use Web Services Enhancements 2.0 (WSE) to provide WS-Security capabilities to your web clients. WSE also provides an ASP.NET SOAP extension to provide support for processing WS-Security headers. Once the SOAP extension is added to your web application’s web.config file, you can interrogate the Security SOAP header for a request using the Security object returned by the Security property of the RequestSoapContext object. A typical usage would be to enumerate the security tokens in the Security header. Among others there are tokens available for Kerberos tickets as well as X.509 certificates so integrating with your existing Kerberos domain or public key infrastructure (PKI) should be straightforward.

WSE also provides support for signing and sealing all or parts of the SOAP envelope. For in-depth information on WSE as well as WS-Security, visit the Web Services Developer Center on MSDN.


© 2004 Kenny Kerr

3 Comments

  • I have used CredUIPromptForCredentials to get user credentials and it works fine, but how secure is protecting that password with the token needed to call the API? It doesn't seem hard tlook through the IL to get the token value and call the same function to get the current password. (Although the hacker would have to execute his app on the same machine)



    Is storing the password encrypted with DPAPI in isolated storage more secure? That would require a hacker to find the file on the harddrive as opposed to just figuring out your token.



    I would like to provide "Remember Me" functionality, but I don't see a very secure way to do it.



    Lawrence Pina

    lpina@colada.com

  • My question was poorly worded. I didn't meant to use token, I meant to comment on the target name parameter to CredUIPromptForCredentials.



    The target parameter would be in the code and could be used by another app to read passwords stored by your app.



    I am just being paranoid I guess. If I was a hacker trying to get a stored password from a program using CredUIPromptForCredentials, I would first look for what the target name is and replay the call with that target name and retrieve the stored password.



    I wrote 2 apps and used the same target name and was able to view the stored password, so securing the target name becomes the problem.



    Thanks for your answer.



    Lawrence

  • Ah yes, the target name. This is not a security feature but merely a key used to identify the credentials that are stored. Ultimately the credentials are still stored securely in the user profile.



    Storing the credentials in an application-specific location in the user profile is really no more secure than what CredUIPromptForCredentials offers. If a hacker is determined enough, he will find where you store your secrets. This is really just security by obscurity, which by itself is no security at all. Ultimately you must trust the code you run in your logon session because such code naturally has all the rights given to the user account.



    This is the traditional model of impersonation and authorization. Code Access Security (CAS) goes in a new direction by placing limits on what code can do. For example, using CAS you can restrict a given application such that it cannot access your secrets.



    If you don’t trust all the code running as a given user, you can require additional entropy (e.g. a password) to be used to encrypt the data and required when decrypting it. This is easily done with the help of CryptProtectData, but defeats the promise of single sign-on.

Comments have been disabled for this content.