Chapter 12: Security
Introduction | Security Threats - Unauthorized Access, Malicious Input – Content Injection and Cross-Site Scripting, Eavesdropping, Message Tampering, and Message Replay, Cross-Site Request Forgery | Web Platform Security - MVC View Encoding, ASP.NET Input Filtering, Protecting Application-Specific Data | Mileage Stats Application Security - Authentication, Configuration, AuthController, DefaultOpenIdRelyingParty, Forms Authentication Sliding Expiration, Input Validation, Anti-Forgery, JSON Hijacking Prevention | Additional Security Considerations - Securing Communication between Client and Server, Protecting Connection Strings, Deploying to a Shared Environment | Summary | Further Reading |
Introduction
This chapter addresses security-related topics for the Mileage Stats Reference Implementation (Mileage Stats) and is divided into three sections. The first section introduces security threats relevant to Mileage Stats. The second section gives a guided tour of Mileage Stats security features that provide countermeasures against those threats. The third section describes possible security modifications to consider if you make changes to the deployment environment and to the security requirements for the application. After reading this chapter, you should understand how relevant security threats are mitigated in Mileage Stats and what some of its extensibility points are.
In this chapter you will learn about:
- Major security threats that you should address in any web application, including unauthorized access, malicious input, content injection, cross-site scripting, eavesdropping, message tampering, message replay, and cross-site request forgery.
- Security features in Mileage Stats that provide countermeasures against the relevant threats, including authentication, input validation, anti-forgery measures, and ways to prevent JavaScript Object Notation (JSON) hijacking.
- Security modifications to accommodate changes in the deployment environment and to the security requirements for the application.
This chapter will also cover some security features of ASP.NET and ASP.NET MVC, and OpenID.
Security Threats
The section describes some security threats that need to be addressed in any web application. If you're already familiar with these security threats and how to mitigate them, skip to the "Mileage Stats Application Security" section where the security features for Mileage Stats are described.
Unauthorized Access
To prevent the wrong people from entering your website and changing its data, you need to limit who can access it. This is typically accomplished by requiring users to authenticate. The most common form of authentication requires a user to provide his or her user name and password as credentials. Once verified, the user has access. If the credentials are not recognized by the website, the user does not have access.
Malicious Input – Content Injection and Cross-Site Scripting
There are a variety of ways to corrupt content by uploading malicious input to your website. Such attacks can not only corrupt the data but can even make your website unusable. If links can be uploaded to a website, malicious users can potentially execute a cross-site scripting (XSS) attack, enabling them to collect potentially sensitive form data and security information. A common way to prevent the uploading of malicious input to a website is to limit the length and type of input that users are allowed to provide. Removing the ability to submit tags by filtering out tag characters ("<" and ">") goes a long way towards preventing malicious users from submitting scripts or HTML tags. As a rule of thumb, acceptable input to the website should be as limited as possible based on expected length, content, and data type for a particular data field.
Eavesdropping, Message Tampering, and Message Replay
Eavesdropping, message tampering, and message replay are grouped together because they are often detected and mitigated by similar measures. A common and relatively simple way to exploit a web application through eavesdropping is to use a network data capture utility to find and record HTTP requests and responses between a website and a client. Without protection from eavesdropping and tampering, an attacker can alter the contents of a captured HTTP request and re-submit it to the website. This type of attack is commonly referred to as a message replay attack. Even if the website requires authentication, it processes the request as if it came from the client because it contains a legitimate security token. HTTP requests can be altered to cause the website to behave undesirably, to delete data, to change data, or to cause large numbers of transactions to be executed. A common way to mitigate message replay in web applications using HTTP is by requiring communication via Secure Sockets Layer (SSL). When you use SSL in a non-anonymous mode, you prevent the ability to replay messages back to the server. Two additional and very important benefits of using SSL are that it prevents any sensitive content in the HTTP traffic from being disclosed to eavesdroppers and prevents message tampering.
Cross-Site Request Forgery
Cross-site request forgery (CSRF, often pronounced as "sea surf",) occurs when malicious commands are sent to a website from the browser of a trusted user. An attacker constructs a seemingly harmless HTML element on a different website that surreptitiously calls the target website and attempts to do something malicious while posing as a trusted user. CSRF has great potential to damage the website being exploited; an attacker can potentially tamper with or delete data, or execute large numbers of unwanted transactions on the targeted website.
This section is an introductory overview and not a substitute for more comprehensive guidance or a threat model. For more information on ASP.NET security, see the ASP.NET Web Application Security reference at the end of this chapter.
Web Platform Security
This section describes some of the out-of-the-box security features that are built into the various components of the web application platform.
MVC View Encoding
The Razor syntax uses the @ operator in Model-View-Controller (MVC) views to specify Microsoft® .NET Framework code. Any output written to the client in the view from code that uses the @ operator is automatically HTML encoded for you. This traps malicious content by preventing it from being rendered back to the client. Trapping malicious content goes hand-in-hand with input filtering, because you should never assume that any data that comes from users or other applications is safe.
ASP.NET Input Filtering
Starting with version 1.1, ASP.NET has provided input filtering out of the box as a secure default. Any attempt to submit a request containing bracketed tags ("<" or ">") in any of its form data, query string parameters, or cookies results in an error page indicating that malicious input has been detected. The following figure is a screenshot of the default ASP.NET input validation failure page.
Input validation failure
While the default ASP.NET input filtering is good, it is very basic. You should still validate user input in your application.
Protecting Application-Specific Data
Sometimes applications host their own data sources instead of accessing them from a centrally hosted location. Whenever you create an application-specific data source, it should be hosted in the App_Data subdirectory. For example, if you add a SQL membership provider for a specific application, the Microsoft SQL Server® Express .mdf file should be created in the App_Data directory. ASP.NET protects application-specific data in a few ways. Files stored in the App_Data folder cannot be accessed directly by clients because, by default, the folder is protected from browsing.
An additional layer of protection for application-specific data files is configured using HTTP handlers in the machine's web.config file. Requests for certain file types are routed to the ASP.NET HttpForbiddenHandler by default. The following code shows the configuration to handle HTTP requests for .mdf and .mdb files that are used by SQL Server Express and Microsoft Access®, respectively, via the HttpForbiddenHandler in the machine's web.config file:
<!-- Contained in machine's default web.config -->
<add path="*.mdf" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
<add path="*.ldf" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
Note that the HTTP handlers defined above are for standard ASP.NET applications. Handlers for an ASP.NET MVC application are defined via route mappings in the application's Global.asax.cs file. All .mdf and .mdb files are normally inaccessible when using ASP.NET MVC unless you explicitly create a route mapping for them; you can also configure ASP.NET MVC to ignore routing for the file types and let ASP.NET handle the file types using the settings described above. Neither approach is recommended for ASP.NET MVC applications because the files should be protected from client browsing by routing all requests to the MVC controllers. For more information on MVC routing best practices, see the reference on "Best Practices for ASP.NET MVC" on the ASP.NET and Web Tools Developer Content Team's blog at the end of this chapter.
Mileage Stats Application Security
This section explains security measures implemented in Mileage Stats that mitigate security threats against it.
Authentication
Mileage Stats implements authentication using a third-party authentication provider (OpenID) and ASP.NET forms authentication tickets to prevent unauthorized access. Whenever an unauthenticated user attempts to access the site, he or she is redirected to the login URL via ASP.NET forms authentication. The following diagram depicts the logical flow for user authentication in Mileage Stats.
Third-party user authentication
- The user attempts to access the site without authenticating first and is redirected to the Mileage Stats home page.
- The user navigates from the Mileage Stats home page to the OpenID sign-in page to authenticate.
- An authentication result is attached to the response by OpenID and picked up by Mileage Stats for processing.
- Mileage Stats converts a successful authentication result from OpenID to an encrypted ASP.NET forms authentication ticket and caches it client-side as a session cookie in the web browser.
- The user accesses the site successfully with a valid forms ticket. Whenever a request is processed with a valid authentication ticket, the expiration time of the ticket is reset to provide a sliding expiration window.
The significance of this authentication model is that a third party (OpenID) is responsible for managing and validating the user's credentials as opposed to more commonly encountered authentication models in which the owner of the website is the same party that maintains and validates the user's credentials.
While out-of-the box ASP.NET forms authentication is not being used, encrypted forms authentication tickets and forms ticket validation are being leveraged programmatically. Forms ticketing is an effective mechanism that you can leverage to work with OpenID authentication instead of creating a security ticketing mechanism from the ground up.
There are several configuration settings and segments of code responsible for implementing authentication end-to-end in Mileage Stats. Let's take a look at how configuration works, and at the major components that make up the authentication mechanism.
Configuration
While the forms tickets are generated manually in Mileage Stats, the code still leverages the configuration settings used for configuring out-of-the-box forms authentication. The following configuration snippet in the Mileage Stats web.config file is responsible for configuring the application for forms authentication and setting the login redirect URL.
<!-- Contained in web.config -->
<authentication mode="Forms">
<forms loginUrl="~/Auth/SignIn" timeout="20" />
</authentication>
The relying party class validates a user's credentials with an authentication provider, then uses them to create a forms authentication ticket. The relying party implementation is configured in the unity.config file.
<!-- Contained in unity.config -->
<!-- NOTE: This is a real openId authentication mechanism -->
<type type="MileageStats.Web.Authentication.IOpenIdRelyingParty, MileageStats.Web"
mapTo="MileageStats.Web.Authentication.DefaultOpenIdRelyingParty,
MileageStats.Web">
<lifetime type="perRequest" />
</type>
There are two unity.config files available, and the one used by Mileage Stats depends on which solution configuration is selected when you compile the application. The one shown above is used with the Release solution configuration. When Debug is selected, a different unity.config file is used that contains a mock authentication relying party class that can be used to get the application up and running. The mock authenticator will put the user through a mock authentication workflow that does not validate any credentials. To deploy the application, we strongly recommend that you compile the application using the Release solution configuration in order to use the actual relying party class that does validate the user credentials.
Both the mock authenticator and the OpenID relying party class implement the IOpenIdRelyingParty interface, which acts as a wrapper around the DotNetOpenAuth interfaces to expose only what is required to interact with the authentication provider and to process the results. Implementing IOpenIdRelyingParty enables you to configure a different relying party implementation if your application requirements change.
AuthController
AuthController is an MVC controller in Mileage Stats that is responsible for handling the user redirect for authentication and converting the response from a successful authentication attempt into an ASP.NET forms authentication ticket. The AuthController uses the relying party implementation specified in the unity.config file. AuthController's SignInWithProvider method is invoked to redirect the user to the authentication provider's sign-in page. The following code snippet shows the SignInWithProvider method.
// Contained in AuthController.cs
public ActionResult SignInWithProvider(string providerUrl)
{
if (string.IsNullOrEmpty(providerUrl))
{
return this.RedirectToAction("SignIn");
}
var fetch = new FetchRequest();
var returnUrl = this.Url.Action("SignInResponse", "Auth", null,
this.Request.Url.Scheme);
try
{
return this.relyingParty.RedirectToProvider(providerUrl, returnUrl, fetch);
}
catch (Exception)
{
this.TempData["Message"] =
Resources.AuthController_SignIn_UnableToAuthenticateWithProvider;
return this.RedirectToAction("SignIn");
}
}
The AuthController's SignInResponse method is invoked to process the response from the user's authentication attempt with the authentication provider. SignInResponse calls the GetResponse method on the relying party class and processes the result. If the result is a successful authentication, AuthController creates an ASP.NET forms authentication ticket and attaches it to the response. If anything other than a successful authentication result is returned from the authentication provider, the user is redirected back to the authentication provider's sign-in page.
// Contained in AuthController.cs
public ActionResult SignInResponse(string returnUrl)
{
var response = this.relyingParty.GetResponse();
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var user = this.userServices.GetOrCreateUser(response.ClaimedIdentifier);
this.formsAuthentication.SetAuthCookie(this.HttpContext,
UserAuthenticationTicketBuilder.CreateAuthenticationTicket(user));
return this.RedirectToRoute("Dashboard");
case AuthenticationStatus.Canceled:
this.TempData["Message"] = "Cancelled Authentication";
return this.RedirectToAction("SignIn");
case AuthenticationStatus.Failed:
this.TempData["Message"] = response.Exception.Message;
return this.RedirectToAction("SignIn");
default:
this.TempData["Message"] =
Resources.AuthController_SignInResponse_Unable_to_authenticate;
return this.RedirectToAction("SignIn");
}
}
The following sequence diagram shows the calls made in AuthController.SignInResponse to authenticate the user with the relying party and to attach the encrypted forms ticket as a cookie if the authentication attempt was successful.
Authentication sequence diagram
- AuthController calls the GetResponse method of its referenced IOpenIdRelyingParty implementation to get the authentication result from the authentication provider.
- AuthController calls the GetOrCreateUser method of its referenced IUserServices implementation.
- AuthController calls the SetAuthCookie method of its referenced IFormsAuthentication implementation.
- AuthController invokes its own RedirectToRoute method and sends the user to its landing page after a successful authentication.
Let's take a closer look at the relying party implementation used in Mileage Stats.
DefaultOpenIdRelyingParty
DefaultOpenIdRelyingParty implements IOpenIdRelyingParty, which is a façade for the OpenIdRelyingParty class provided as part of DotNetOpenAuth that validates the user's credentials with OpenID. The following code snippet shows the RedirectToProvider method on the DefaultOpenIdRelyingParty class, which is responsible for redirecting the user to the authentication provider's login page.
// Contained in DefaultOpenIdRelyingParty.cs
public ActionResult RedirectToProvider(string providerUrl, string returnUrl,
FetchRequest fetch)
{
IAuthenticationRequest authenticationRequest =
this.relyingParty.CreateRequest(providerUrl, Realm.AutoDetect,
new Uri(returnUrl));
authenticationRequest.AddExtension(fetch);
return
new OutgoingRequestActionResult(authenticationRequest.RedirectingResponse);
}
Forms Authentication Sliding Expiration
Sliding expiration of the forms authentication ticket in Mileage Stats is accomplished by resetting the expiration time of the ticket whenever the user makes a new request to the server. Normally, the reset is enabled by setting the slidingExpiration attribute to true on the forms security configuration in the web.config file; however, because the ticket is being manually created and attached to the response in the AuthController, the ticket needs to be refreshed manually. A custom handler for the HttpApplication.PostAuthenticateRequest event implements the sliding expiration for the forms ticket.
// Contained in Global.asax.cs
private void PostAuthenticateRequestHandler(object sender, EventArgs e)
{
HttpCookie authCookie =
this.Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (IsValidAuthCookie(authCookie))
{
var formsAuthentication =
ServiceLocator.Current.GetInstance<IFormsAuthentication>();
var ticket = formsAuthentication.Decrypt(authCookie.Value);
var mileageStatsIdentity = new MileageStatsIdentity(ticket);
this.Context.User = new GenericPrincipal(mileageStatsIdentity, null);
// Reset cookie for a sliding expiration.
formsAuthentication.SetAuthCookie(this.Context, ticket);
}
}
The advantage of using a forms ticket with a sliding expiration is that it does not force the user to authenticate again if he or she maintains a reasonable level of activity in the application. Otherwise, the user would be redirected to the authentication page after a fixed amount of time had elapsed after the original authentication. While sliding expiration greatly enhances the usability of the application, it is also a potential security risk because the user's authenticated session can be kept alive indefinitely by submitting requests to the server before the sliding expiration time on the forms ticket has passed. This risk can be mitigated by introducing an additional timeout value that does not slide, after which the ticket will expire regardless of user activity. While this approach is effective, it was not implemented in Mileage Stats because it would add complexity to the forms ticket handling that is beyond the scope of the application.
Input Validation
One of the primary ways of preventing an application from accepting malicious content is validating any input before it is accepted by the application. While out-of-the-box ASP.NET input validation does a good job of preventing script or HTML injection, it is not always practical to use this mechanism in an ASP.NET MVC application. In Mileage Stats, this mechanism is disabled to handle input validation directly within the MVC model classes. If you don't implement your own input validation and rely on the built-in ASP.NET input validation instead, two things will happen. First, the input will not be validated until after the controller has processed it and before the view has rendered. Next, you will get the default "yellow screen of death" input validation page, which is not a pleasant user experience. When the out-of-the box ASP.NET input validation is disabled, you also lose HTML encoding on your input. To account for this, the @ operator in Razor syntax automatically HTML-encodes output that is rendered in an MVC view.
Although input validation can be done on the client side to reduce round trips to the server, it must also be performed on the server because client-side validation can be bypassed by an attacker. One of the advantages of ASP.NET MVC is that it can be configured to render client-side validation based on server-side validation attributes defined on MVC model properties. This provides a single point in the application to define and maintain data validation rules. For example, in Mileage Stats, the _ProfileForm view is configured to use the MileageStats.ServicesModel.User class as its MVC model via the @model directive. If you look at the DisplayName property on the class, you will see attributes that limit the length of the value, that require a value for the property, and that use a custom text input validator that filters input using a regular expression.
// Contained in User.cs
[StringLength(15,
ErrorMessageResourceName = "UserDisplayNameStringLengthValidationError",
ErrorMessageResourceType = typeof(Resources))]
[TextLineInputValidator]
[Required(AllowEmptyStrings = false,
ErrorMessageResourceName = "UserDisplayNameRequired",
ErrorMessageResourceType = typeof(Resources))]
[Display(Name = "UserDisplayNameLabelText", ResourceType = typeof(Resources))]
public string DisplayName { get; set; }
Here is the custom input validator class that is behind the TextLineInputValidator attribute.
//Contained in TextLineValidatorAttribute.cs
public class TextLineInputValidatorAttribute : RegularExpressionAttribute,
IClientValidatable
{
public TextLineInputValidatorAttribute()
: base(Resources.TextLineInputValidatorRegEx)
{
this.ErrorMessage = Resources.InvalidInputCharacter;
}
public IEnumerable<ModelClientValidationRule>
GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = Resources.InvalidInputCharacter,
ValidationType = "textlineinput"
};
rule.ValidationParameters.Add("pattern", Resources.TextLineInputValidatorRegEx);
return new List<ModelClientValidationRule>() { rule };
}
}
The TextLineInputValidatorAttribute class uses a set of regular expressions it loads from the resources file. If any of the regular expression patterns are matched in the input, it fails validation. The regular expression pattern used to validate text input is ^(?!.*--)[A-Za-z0-9\.,'_ \-]*$. This limits the text assigned to the property to alphanumeric characters and allows only a limited range of punctuation characters. Notice that the regular expression pattern is used to validate legitimate characters rather than to exclude invalid characters. By default, anything not explicitly defined in the regular expression is excluded from the application data. The list that limits input by only allowing what is known to be valid is commonly referred to as a safe list. The advantage of safe lists is that anything that falls outside of the valid set of characters is not allowed; you don't have to worry about accidentally omitting a character from the regular expression that shouldn't be allowed in the application input.
What's nice about this validation mechanism is that the client-side validation for these properties is generated for you based on the server-side validation defined in the MVC model when the HTML for the MVC view is rendered. The following code shows you what the rendered client-side validation looks like for the user display name form field when you view the HTML source for the page.
<!-- Rendered HTML in the client browser -->
<input data-val="true"
data-val-length="Display name must be less than 15 characters."
data-val-length-max="15" data-val-required="Display name is required."
data-val-textlineinput="Only alpha-numeric characters and [.,_-&#39;] are allowed."
data-val-textlineinput-pattern="^(?!.*--)[A-Za-z0-9\.,'_ \-]*$"
id="DisplayName"
maxlength="15"
name="DisplayName"
type="text"
value="Sample User" />
Note
If you want to accept HTML tags or other types of input that would normally be rejected by simple input validation, you can leverage the regular expression/input validation mechanism to do it. However, you will need to create more sophisticated patterns to allow the input you want to be accepted while still excluding the input that you don't want submitted to your application.
Anti-Forgery
As previously explained in the "Security Threats" section, CSRF has a high damage potential and should be prevented. ASP.NET MVC has a simple yet effective mechanism for mitigating CSRF attacks. In your MVC view content, add an @Html.AntiForgeryToken directive to the view, as shown here.
<!-- Contained in _ProfileForm.cshtml -->
@model MileageStats.ServicesModel.User
@Html.AntiForgeryToken()
When the view HTML is rendered to the client, MVC places a unique value for the user in a hidden form field and in a session cookie. The form field in the rendered HTML might look like this.
<!-- Rendered HTML in the client browser -->
<input name="__RequestVerificationToken"
type="hidden"
value="H4zpQFvPdmEdGCLsFgeByj0xg+BODBjIMvtSl5anoNaOfX4V69Pt1OvnjIbZuYrpgzWxWHIjbn
zFOLxP5SzVR4cM9XZeV78IPi8K4ewkM3k2oFkplrXL4uoAqy+aoSOg8s1m1qxrE7oeBBtvezEHCAs6nKE
h2jAwn3w0MwmhkcDQiJfJK7hGvN0jXA4d7S8x7rbLxp4Y8IJZS9wka2eOLg==" />
There's another piece to implementing the anti-forgery tokens in ASP.NET MVC. You must also put a ValidateAntiForgeryToken attribute on corresponding MVC controller actions that you want to protect. The following code is an example of the attribute decorating the edit action on the ProfileController class.
// Contained in ProfileController.cs
[HttpPost]
[ValidateInput(false)]
[ValidateAntiForgeryToken]
public ActionResult Edit(User updatedUser)
When ASP.NET MVC checks for a request forgery, it verifies that the request verification token form field and cookies are present and that the values match each other. If either the cookie or the form field values are missing, or the values don't match, ASP.NET MVC does not process the action and returns an authorization failure instead.
Note
MVC anti-forgery tokens do not work using HTTP GET requests because the _RequestVerificationToken value from the client needs to be sent as a posted value. Therefore, it is important to make sure that you only accept client requests that use HTTP POST when you want to implement anti-forgery tokens. This shouldn't be an issue because you should already be using HTTP POST only for data updates, and using HTTP GET exclusively for read-only operations.
Make sure that you are using the same salt values in the @Html.AntiForgeryToken directive in the MVC model and in the parameter for the ValidateAntiForgeryToken attribute on the MVC controller action; otherwise, the anti-forgery token will not validate properly when the MVC action is called. For more information about using a salt with an MVC anti-forgery token, see the reference to "Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC's**AntiForgeryToken()**helper" at the end of the chapter.
JSON Hijacking Prevention
In some situations, it may be possible for an attacker to access data via a JSON request by using an attack that closely resembles a CSRF attack. If an attacker can get a user to click on a malicious link that makes a JSON request via HTTP GET and returns a JSON array, it may dump the contents of the array in the response, making it accessible to the attacker. The mitigation for JSON hijacking is fairly straightforward: either you can make sure to never return JSON arrays in a response, or you can restrict JSON requests to respond only to requests that use the HTTP POST action. To configure a JSON action on an MVC controller to respond to requests only via HTTP POST, add the HttpPost attribute to it. The following code shows the HttpPost attribute on the ProfileController's JsonEdit action.
// Contained in ProfileController.cs
[HttpPost]
[ValidateInput(false)]
public ActionResult JsonEdit(User updatedUser)
This may not be an issue if you don't care whether or not someone can gain unauthorized access to data that is not sensitive; however, as a general practice you should protect your JSON calls against hijacking. For more information, see the reference to JSON Hijacking in the "Further Reading" section at the end of the chapter.
Additional Security Considerations
This section describes other security issues to consider when deploying and configuring Mileage Stats. Securing communication between the client and the server is critical for protecting the data that goes back and forth between them. You may also want to use a different method of authenticating users for the application, whether that means using a different relying party for the existing authentication mechanism or switching to an ASP.NET membership provider. If you expand the functionality of your application, you may want to restrict different levels of functionality to a limited subset of application users. In some cases, you may want to deploy the data tier to SQL Server or SQL Server Express instead of running it on SQL Server Compact Edition.
Securing Communication between Client and Server
When properly configured, Secure Sockets Layer (SSL) is an effective way to prevent eavesdropping, session hijacking, message replay and tampering between the client and the server. Mileage Stats does not use SSL out of the box because the use of SSL depends on your infrastructure and not on the application itself. There's already a lot of documentation on how to set up SSL on your server, so the specifics won't be covered here. For more information, see the reference on "How to Set Up SSL on IIS 7" in the "Further Reading" section at the end of this chapter. Make sure that when you configure SSL on your server that you do not configure anonymous SSL. Otherwise, communication between the client and server will still be susceptible to several different attacks.
Note
Warning: When communicating with the server via SSL, a client verifies the identity of the server with which it is communicating by checking the host name provided in the SSL certificate against the URL of the server. Sometimes the client does not have a certificate that can be used to identify the server, and sometimes the server is configured to use SSL protocols that do not incorporate identification of the server; these are examples of anonymous SSL. Anonymous SSL can be configured intentionally on some web servers but not on others. It can be "forced" intentionally by breaking the mechanisms inherent to SSL that tie a host to the identity specified in an SSL certificate. While this provides protection against tampering and eavesdropping, it does not protect against message replay or spoofing attacks, where one party can pose as someone else during the communication.
Once SSL has been set up in your environment, there are two changes to the web.config file that you'll need to make in Mileage Stats. The first change is to require that ASP.NET forms authentication use SSL. To do this, the requireSSL attribute needs to be added to the forms element.
<!-- Contained in web.config -->
<authentication mode="Forms">
<forms loginUrl="~/Auth/SignIn" timeout="20" requireSSL="true"/>
</authentication>
The default timeout for a forms authentication ticket is 20 minutes. However, you can adjust this value to a reasonable time that preserves the usability of the sliding expiration but minimizes the security risk of indefinitely authenticated users.
The second change is for OpenID to require an SSL-encrypted communication with the relying party.
<!-- Contained in web.config -->
<openid>
<relyingParty>
<security requireSsl="true" />
The value of requireSsl needs to be set to true. That way you can ensure that the authentication provider will only attempt to communicate with your application via HTTPS.
Any use of the UrlHelper.Action method that specifies a protocol (such as HTTP) in your application for constructing URLs will need to be updated to HTTPS for any URL references that are now secured via SSL. For more information on the UrlHelper.Action method, see the reference on the UrlHelper.Actionmethod at the end of the chapter.
Protecting Connection Strings
If you change data sources for the application, you will likely end up changing the connection string in the web.config file. As a good practice, you should protect these connection strings from discovery. For more information on protecting connection strings, see the reference on Protecting Connection Information (ADO.NET) at the end of the chapter.
Deploying to a Shared Environment
In situations where you are deploying your web application to a server that is being shared by multiple parties, you may want to protect your application from the other applications that are hosted on the server. In addition to protecting sensitive information in the web.config file, ensure that the application pools on the server are configured to run per web application and that the temporary ASP.NET files cached on the server are in a location that is not shared with the other web applications on the server. For more information, see "How To: Secure an ASP.NET Application on a Shared Server" on MSDN®.
Summary
This chapter provided an overview of security threats that impact Mileage Stats. These include: unauthorized access, malicious input (content injection and cross-site scripting), eavesdropping, message tampering, message replay, and cross-site request forgery. The chapter also described how these threats are mitigated through various security features. Finally, a few ideas were suggested for extending or changing security for Mileage Stats to accommodate different deployment environments and security requirements.
Further Reading
"ASP.NET Web Application Security" on MSDN:
https://msdn.microsoft.com/en-us/library/330a99hc.aspx
"How to set up SSL on IIS 7":
http://learn.iis.net/page.aspx/144/how-to-set-up-ssl-on-iis-7/
"UrlHelper.Action Method" on MSDN:
https://msdn.microsoft.com/en-us/library/dd505232.aspx
"Understanding the Forms Authentication Ticket and Cookie" on Microsoft Support:
https://support.microsoft.com/kb/910443
"Authenticating Users with Forms Authentication" on ASP.NET:
https://www.asp.net/mvc/tutorials/authenticating-users-with-forms-authentication-cs
Protecting Connection Information (ADO.NET) on MSDN:
https://msdn.microsoft.com/en-us/library/89211k9b(v=VS.100).aspx
"How To: Secure an ASP.NET Application on a Shared Server" on MSDN:
https://msdn.microsoft.com/en-us/library/ms228096.aspx
"A Guide to Claims–based Identity and Access Control" on MSDN:
https://msdn.microsoft.com/en-us/library/ff423674.aspx
"OWASP Top 10 Project" on OWASP.org:
https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project
"JSON Hijacking" on Phil Haack's blog:
http://haacked.com/archive/2009/06/25/json-hijacking.aspx
"Best Practices for ASP.NET MVC" on the ASP.NET and Web Tools Developer Content Team's blog: https://blogs.msdn.com/b/aspnetue/archive/2010/09/17/second_2d00_post.aspx
"Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC's AntiForgeryToken() helper" on Steve Sanderson's blog:
http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/
"Hack Proofing Your Microsoft ASP.NET Web Forms and MVC Applications" by Adam Tuliper on Channel 9:
https://channel9.msdn.com/Events/TechEd/NorthAmerica/2011/DEV333