In my role as a consultant with Microsoft Services, I have regular discussions with customers and partners about application security. In this article, I'll explore some of the themes that arise in those discussions. In particular, I'll focus on the new challenges programmers face when trying to secure Silverlight applications, and I'll consider where development teams should focus their resources.
This article touches on many technical concepts that you'll find covered in more detail elsewhere (including this magazine). For this reason, I won't explore these topics in great technical depth. Instead, the goal of the article is to "connect the dots" and show how you can exploit these concepts to secure your applications.
When planning security for an application, it's useful to think of three As: authentication, authorization and audit. Authentication is the act of confirming that users are who they claim to be. We usually do this with a user name and password. Authorization is the process of confirming that a user, once authenticated, actually has the appropriate permissions to perform a particular action or access a particular resource. Audit is the act of maintaining a record of activity such that actions and requests made upon a system can't be denied by the user.
Unlike ASP.NET, Silverlight is a client-side technology, so it runs on users' computers. So Silverlight development arguably has more in common with Windows Forms or WPF than with ASP.NET. In many ways, this is one of Silverlight's greatest advantages, as it removes many of the problems caused by the stateless nature of Web applications. However, because all the UI code runs on client computers, you can't trust it anymore.
Unlike Windows Forms, Silverlight operates within the browser sandbox and has a reduced set of capabilities, so it provides an increased degree of security (though in Silverlight 4, users can identify certain applications as trusted and promote the programs' privileges to allow COM interop). Because of this, Silverlight can't connect to a database directly, so you must create a layer of services that provide access to your data and business logic.
Typically, you host these services on your Web server, just as you would with your ASP.NET Web forms, for example. Given that Silverlight code runs on the wrong side of the trust boundary between your servers and the real world (see Figure 1), the focus of your team's effort should always be to secure the services.
Figure 1 Silverlight Runs on the Wrong Side of the Trust Boundary
There's little point in implementing rigorous security checks within your Silverlight code itself. After all, it would be easy for an attacker to do away with the Silverlight application altogether and invoke your services directly, side-stepping any security measures you implemented. Alternatively, a malicious person could use a utility like Silverlight Spy or Debugging Tools for Windows to change the behavior of your application at runtime.
This is an important realization—a service can't know for sure what application is invoking it or that the app hasn't been modified in some way. Therefore your services have to ensure that:
• The caller is properly authenticated
• The caller is authorized to perform the requested action
For those reasons, most of this article focuses on how to secure services in a way that's compatible with Silverlight. Specifically, I'll consider two different types of services hosted via ASP.NET in Microsoft IIS. The first type, services created using Windows Communication Foundation (WCF), provides a unified programming model for building services. The second, WCF Data Services (formerly ADO.NET Data Services), builds on WCF to let you rapidly expose data using standard HTTP verbs, an approach known as Representational State Transfer (REST).
Naturally, if security is a concern, it's always wise to encrypt any communication between clients and servers. The use of HTTPS/SSL encryption is recommended and assumed throughout this article.
Today, the two most common authentication methods Web developers use on the Microsoft platform are Windows authentication and forms authentication.
Windows authentication leverages the Local Security Authority or Active Directory to validate user credentials. This is a big advantage in many scenarios; it means you can centrally manage users with tools already familiar to systems administrators. Windows authentication can use any scheme supported by IIS including basic, digest, integrated authentication (NTLM/Kerberos) and certificates.
The integrated scheme is the most common choice for use with Windows authentication, because users don't have to provide their user names and passwords a second time. Once a user logs on to Windows, the browser can forward credentials in the form of a token or a handshake that confirms the persons identity. There are some disadvantages to using integrated authentication, because both the client and server need visibility ofthe user's domain. As a result, it's best targeted at intranet scenarios. Furthermore, though it works with Microsoft Internet Explorer automatically, other browsers, such as Mozilla Firefox, require additional configuration.
Both basic and digest authentication typically require users to re-enter their user names and passwords when they initiate a session with your Web site. But because both are part of the HTTP specification, they work in most browsers and even when accessed from outside your organization.
Silverlight leverages the browser for communication, so Windows authentication is easy to implement with any of the IIS authentication methods just discussed. For a detailed description of how to do so, I recommend reading the step-by-step guide "How to: Use basicHttpBinding with Windows Authentication and Transport-CredentialOnly in WCF from Windows Forms" at msdn.microsoft.com/ library/cc949012. This example actually uses a Windows Forms test client, but the same approach applies to Silverlight.
Forms authentication is a mechanism that provides simple support for custom authentication in ASPNET. As such, it's specific to HTTP, which means it's also easy to use in Silverlight.
The user enters a user name and password combination, which is submitted to the server for verification. The server checks the credentials against a trusted data source (often a database of users), and ifthey're correct, returns a FormsAuthentication cookie. The client then presents this cookie with subsequent requests. The cookie is signed and encrypted, so only the server can decrypt it— a malicious user can neither decrypt nor tamper with it.
Exactly how you invoke forms authentication varies depending on how you implement your login screen. For example, if you've used an ASP.NET Web form that redirects to your Silverlight application after the user's credentials have been validated, you probably have no more authentication work to do. The cookie already will have been sent to the browser and your Silverlight application will continue to use the cookie whenever making a request to that domain.
If, however, you want to implement the login screen inside your Silverlight application, you'll need to create a service that exposes your authentication methods and sends the appropriate cookie. Fortunately, ASP.NET already provides what you need—the authentication service. You just need to enable it in your application. For detailed guidance, I recommend reading "How to: Use the ASP.NET Authentication Service to Log In through Silverlight Applications" at msdn.microsoft.com/libra ry/dd560704(VS.96).
Another great feature of ASP.NET authentication is its extensibility. A membership provider describes the mechanism by which the user name and password are verified. Fortunately, there are a number of membership providers available as part of ASP.NET, including one that can use SQL Server databases and another that uses Active Directory. However, if a provider that meets your requirement isn't available, it's straightforward to create a custom implementation.
Once your users are authenticated, it's important to ensure that only they can attempt to invoke the services. Both ordinary WCF services and WCF Data Services are represented by a .svc file in ASP.NET applications. In this example, the services are going to be hosted via ASP.NET in IIS, and I'll demonstrate how you can use folders to secure access to the services.
Securing .svc files this way is a little confusing because, by default, a request for such a file actually skips most of the ASP.NET pipeline, bypassing the authorization modules. As a result, to be able to rely on many ASP.NET features, you'll have to enable ASPNET compatibility mode. In any case, the WCF Data Services mandate that you enable it. A simple switch inside your configuration file achieves the task:
With ASP.NET compatibility enabled, it's possible to prevent access to unauthenticated users by using the authorization section of a web.config file, also shown in the previous code snippet.
When using forms authentication, the developer must think carefully about which parts of the site need to be accessible, even to unauthenticated users. For example, if all parts are restricted to authenticated users only, how will an unauthenticated user log in?
It's often easiest to create a folder structure that supports your basic authorization requirements. In this example, I've created a "Secured" folder that contains the MyWcfService.svc and MyWcf-DataService.svc files, and I've deployed a web.config file. In Figure 2 you can see the folder structure, and the previous code snippet shows the contents of the web.config file.
Figure 2 Secured Folder Containing the Web.config File
Note that the root of the application must have anonymous access allowed, otherwise users won't be able to reach the login page.
For sites using Windows authentication, things can be somewhat simpler in this respect, as authentication takes place before the user gets to the resources contained within the application, so there's no need for a specific login page. Using this approach, it's actually possible to restrict access to services in a more detailed way, allowing only specific groups of users or roles to access resources. For more information, see 'ASPNET Authorization" (msdn.microsoft.com/library/wce3kxhd).
This example implements authorization somewhat, but folder-level authorization alone is far too coarse-grained to rely on for most scenarios.
Authorization in WCF Services
Using the PrincipalPermission attribute is an easy way to demand that an invoker of a Microsoft .NET Framework method be within a specific role. This code sample demonstrates how this might be applied to a ServiceOperation in WCF where the calling user must be part of the "OrderApprovers" role:
This is easily implemented in applications that use Windows authentication to leverage the existing facility to create Active Directory groups for organizing users. With applications using forms authentication, it's possible to leverage another great provider-based feature of ASP.NET: RoleProviders. Again, there are a number of these available, but if none are suitable, you can implement your own.
Of course, even per-method authorization is rarely enough to meet all your security needs, and you can always fall back to writing procedural code inside your services as shown in Figure 3.
Figure 3 Using Procedural Code to Implement Specific Authorization
WCF is a highly extensible platform, and as with all things in WCF, there are many approaches to implementing authorization in your services. Dominick Baier and Christian Weyer discussed a number of the possibilities in detail in the October 2008 issue of MSDN Magazine. The article, "Authorization in WCF-Based Services" (msdn.microsoft.com/magazine/cc948343), even ventures into claims-based security, a structured way of organizing the authorization in your application.
Authorization in WCF Data Services
WCF Data Services, as the name suggests, builds on WCF to provide REST-based access to a data source—perhaps most often a LINQ-to-SQL or LINQ-to-Entity Framework data source. In brief, this lets you provide access to your data using a URL that maps to the entity sets exposed by your data source (an entity set typically maps to a table in your database). Permissions to these entity sets can be configured inside the services code-behind file. Figure 4 shows the content of the MyWcfDataService.svc.cs file.
Figure 4 A WCF Data Services Code-Behind File with Configuration of Entity Set Access Rules
Here, I've given Read permissions over the Orders entity set and configured the Products entity set to allow full reading, the inserting of new records and the deletion of existing records.
However, because WCF Data Services automatically renders access to your data based on this configuration, you don't have direct access to the code, so there's no obvious place to implement any specific authorization logic. WCF Data Services supports interceptors that allow developers to implement logic between the client and the data source. For example, it's possible to specify a query interceptor that filters the results for a particular entity set. The example in Figure 5 shows two query interceptors added to the MyWcfDataService class.
The first is applied to the Products entity set and ensures that users can retrieve only products created by them. The second ensures that only users in the PrivateOrders role can read orders flagged Private.
Likewise, it's possible to specify change interceptors that run before an entity is inserted, modified or deleted as demonstrated here:
Figure 5 Query Interceptors in WCF Data Services
On initial viewing, the OnChangeProducts change interceptor in this code sample appears to expose a security vulnerability, because the implementation relies on data passed from an external source—specifically the "product" parameter. But when deleting an entity in WCF Data Services, only an entity key is passed from the client to the server. That means the entity itself, in this case the Product, has to be fetched again from the database and therefore can be trusted.
However, in the case of an update to an existing entity (for example, when the operations parameter equals UpdateOperations.Change),
the product parameter is the de-serialized entity sent by the client, therefore it can't be trusted. The client application may have been modified to specify the CreatedBy property of this particular product to a malicious user's own identity, thereby elevating the usurpers privileges. That could allow modification of a product by an individual who shouldn't be able to do so. To avoid this, I recommend that you re-fetch the original entity from the trusted data source based on the entity key alone, as shown in Figure 6.
Figure 6 A Change Interceptor Preventing Unauthorized: Insert, Update and Delete Operations
Because this implementation relies so much on the CreatedBy property of the Product entity, it's critically important that this is enforced in a reliable way from the moment the data is created. Figure 6 also shows how this might be achieved by overriding any value passed by the client for an Add operation.
Note that as the example currently stands, handling operations of type UpdateOperations.Change wouldn't be an issue. In Figure 4, the service was configured to allow only AllRead, WriteAppend (insert) and WriteDelete actions to occur on the Products entity sets. Therefore, the ChangeInterceptor would never be invoked for a Change operation, as the service would immediately reject any request to modify a Product entity at this endpoint. To enable updates, the call to SetEntitySetAccessRule in Figure 4 would have to include WriteMerge, WriteReplace or both.
The Silverlight plug-in can make cross-domain HTTP requests. A cross-domain call is an HTTP request made to a domain other than the one from which the Silverlight application was downloaded. The ability to make such calls has traditionally been viewed as a security vulnerability. It would allow a malicious developer to make requests to another site (for example, your online banking site) and automatically forward any cookies associated with that domain. Potentially, this could give the attacker access to another logged-in session within the same browser process.
For this reason, sites have to opt in to allowing cross-domain calls through the deployment of a cross-domain policy file. This is an XML file that describes what types of cross-domain calls are allowed—for example, from what domain to what URLs. For more information, see "Making a Service Available Across Domain Boundaries" (msdn.microsoft.com/library/cc197955(VS.95)).
You should always exercise caution when deciding to expose any sensitive information to cross-domain calls. But if you decide this is a scenario you need to support alongside authentication, it's important to note that cookie-based authentication methods—like forms authentication described earlier—are no longer suitable. Instead, you could consider leveraging message credentials, where the user name and password are passed to the server and validated with every call. WCF supports this through the TransportWith-MessageCredential security mode. For more information, see "How to: Use Message Credentials to Secure a Service for Silverlight Applications" (msdn.microsoft.com/library/dd833059(VS.95)).
Of course, this approach removes ASP.NET from the authentication process altogether, so it's difficult to leverage alongside ASPNET authorization, discussed earlier.
Securing Your Silverlight XAP files
People concerned about Silverlight security often ask, "How can I protect my XAP files?" Sometimes the motivation behind this query is to protect the intellectual property contained within the code. In this case, you'll need to look at obfuscation to make it more difficult for people to understand your code.
Another common motivation is to prevent malicious users from interrogating the code and understanding how the Silverlight application works—giving them the potential to break into your services.
I usually respond to this with two points. First, although it's possible to restrict the download ofyour Silverlight application (.xap file) to authenticated and authorized users only, there's no reason to trust these users to be any less malicious than an unauthenticated user. Once the application has been downloaded to the client, there's absolutely nothing to stop users from interrogating the code in an attempt to elevate their own privileges or forward the libraries to somebody else. Obfuscation may make this process a little more difficult, but it isn't good enough to make your application secure.
If you're using Windows authentication, then you can easily protect your .xap files using IIS Directory Security. If, however, you're using forms authentication, then things get a little more complicated. In this case, it's up to the FormsAuthenticationModule to intercept and verify the cookie accompanying any request and allow or deny access to the requested resource.
Because the FormsAuthenticationModule is an ASP.NET module, the request must pass through the ASPNET pipeline for this inspection to take place. In IIS6 (Windows Server 2003) and previous versions, requests for .xap files will not, by default, be routed via ASP.NET.
IIS7 (Windows Server 2008), though, introduced the Integrated Pipeline, which allows all requests to be routed through the ASPNET pipeline. If you can deploy to IIS7 and use an application pool running in Integrated Pipeline mode, then securing your .xap files is no more difficult than securing your .svc files as described earlier in the ASP.NET Authorization section. But if you have to deploy to IIS6 or earlier, you probably have some additional work to do.
One popular approach involves streaming the bytes that make up your .xap file through another extension that the ASP.NET pipeline does handle. The typical way to do this is via an IHttpHandler implementation (in an .ashx file). For more information see "Introduction to HTTP Handlers" (msdn.microsoft.com/library/ms227675(VS.80)).
Another approach is to change the configuration of IIS so that .xap files are routed through the ASP.NET pipeline. However, because this requires a nontrivial change to your IIS configuration, the former approach is more common.
Another issue to consider with forms authentication is the login screen. If, as proposed earlier in this article, you opt for an ASP.NET Web form, then there are no problems. But if you'd prefer the login screen to be authored in Silverlight, you'll need to break the application into parts. One part (the login module) should be available to unauthenticated users, and another (the secured application) should be available to authenticated users only. You can take two approaches:
1. Have two separate Silverlight applications. The first would contain the login dialog and be in an unsecured area ofthe site. On successful login, this would then redirect to a page specifying a .xap file in a secure area of your site.
2. Break your application into two or more modules. The initial .xap, located in an unsecured area of your site, would perform the authentication process. If successful, that .xap file would request a subsequent one from a secure area that could be dynamically loaded into the Silverlight application. I recently blogged about how you can do this (thejoyofcode.com/ How_to_download_and_crack_a_Xap_in_Silverlight.aspx).
JOSH TWISTis a principal consultant with the Microsoft Application Development Consulting team in the United Kingdom and can be found blogging at thejoyofcode.com.