- Requiring authentication and authorization
- Preventing cross-site scripting attacks
- Mitigating cross-site request forgeries
- Avoiding JSON hijacking
Security is often a vague and amorphous topic in web application development. We rely on the web server to keep our application secure, and we rely on our programming platform. The rest sometimes seems theoretical and rare. In this chapter, we’ll describe possible attacks and exactly what to do to prevent them by using two main approaches.
The first is traditional management of authentication and authorization. Authentication is ensuring that the user has supplied the proper credentials to access the system. When a user logs in, usually by providing a username and password, he is authenticated. Authorization is making a decision about whether a given user has permission to do something with the system. When a user accesses a resource not available to other users, he has been specifically authorized to do so.
The second approach we’ll discuss involves common web attack vectors and technical vulnerabilities that allow attackers to bypass authentication or authorization. There are several attack vectors, but we’ll focus on some common ones: cross-site scripting (XSS), cross-site request forgery (XSRF), and a special cross-site request forgery called JSON hijacking.
ASP.NET MVC 2 ships with a filter attribute called AuthorizeAttribute that provides out-of-the-box authentication and authorization. Developers can apply the attribute to actions to restrict access to them. If the user isn’t permitted to access the action, the AuthorizeAttribute will transmit an HTTP status code of 401 Unauthorized to the browser, indicating that the request has been refused. Applications using ASP.NET’s forms authentication mechanism and with a login page specified in Web.config will then redirect the browser to the login page, and users may only proceed once they have been authenticated.
The simplest use of AuthorizeAttribute, shown in listing 11.1, only requires that the current user be authenticated.
When this action is requested by an unauthenticated user, AuthorizeAttribute, applied to the About action , will prevent access to it.
To restrict an action further, developers can specify users or roles that AuthorizeAttribute requires. These roles or users are passed to the attribute using a comma-delimited list of strings containing either the usernames or the roles allowed. Listing 11.2 shows the AuthorizeAttribute syntax for requiring a specific user.
[Authorize(Users = "admin")]
public ActionResult Admins()
Hard-coding a username like this may be too tightly controlling. Users come and go, and the duties of a given user may change during their time using the application. Instead of requiring a specific user, it usually makes sense to require a role.
Listing 11.3 demonstrates how developers can use AuthorizeAttribute to restrict an action to certain roles.
[Authorize(Roles = "admins, developers")]
public ActionResult Developers()
Access to the Developers action will only be allowed to users in the admins or developers roles—all other users (authenticated or not) will be issued a 401 response code and, using ASP.NET’s forms authentication, will be redirected to the login page.
Now that we’ve seen a few examples of how AuthorizeAttribute is used, let’s talk about how it works.
AuthorizeAttribute checks the IPrincipal associated with the current HttpCon-text. When Users or Roles is specified, it ensures the IPrincipal’s username is in the allowed usernames or is a member of one of the granted roles. The AuthorizeAt-tribute can be used in a few ways:
- If AuthorizeAttribute is applied to a controller, it’s applied to every action in that controller.
- If multiple AuthorizeAttributes are applied to an action, all checks occur and the user must be authorized by all of them.
- AuthorizeAttribute implements a special interface called IAuthorization-Filter. When applied to an action, an IAuthorizationFilter will execute before any other action filters, and before the normal result. Listing 11.4 shows the declaration of IAuthorizationFilter.
public interface IAuthorizationFilter
void OnAuthorization(AuthorizationContext filterContext);
If you wanted to create your own filter attribute for authentication or authorization, you could implement the IAuthorizationFilter interface as an action filter and apply it to an action.
AuthorizeAttribute does its security check in the OnAuthorization method, and sets the AuthorizationContext’s Result property to HttpUnauthorizedResult—the mechanism for returning the 401 status code.
There are several other IAuthorizationFilter implementations in ASP.NET MVC; all are used to short-circuit the normal response to protect against undesired requests. Chapter 9 covered filters, and these five filters deal specifically with security:
We’ve seen how AuthorizeAttribute can help us manage authentication and authorization, so now let’s turn our attention to other, more insidious attack vectors. Although authentication and authorization checks prevent hapless visitors from accessing secure areas, we still must protect our application from hackers and thieves who attempt to exploit vulnerabilities inherent in web applications.
Traditionally that malicious script sends a request to a third-party site containing sensitive data. That’s the cross-site part. A user puts a script on one site that sends secret data to another conspiring site. The trick for the hacker is to get the script to run on the vulnerable site.
In the source code for this book, we’ve included a sample Visual Studio solution that you can run to perform a simulated, local XSS attack. It contains two simple ASP.NET MVC 2 applications. One is vulnerable to XSS attacks in several widely used browsers.
When the example Visual Studio solution is run (typically with Ctrl-F5), two sites appear in the web browser. The vulnerable site sets a cookie, ostensibly containing sensitive data. The second site is the attacker, and it will collect the data from our evil request. The attacking site has a page that should read “No victims yet.” After we initiate our attack, it will display the secret cookie.
On the vulnerable site, the cookie has been set with the code in listing 11.5, which is traditional cookie-setting code.
public ActionResult Index()
var cookie = new HttpCookie("mvcinaction", "secret");
With the cookie created, we can play the part of the hacker on the comments page, as shown in figure 11.1.
We included a button that will automatically insert a malicious comment in the Comment text area. The comment appears in listing 11.6.
This comment includes a script block that writes HTML to the browser. The HTML contains an image whose SRC attribute isn’t an image at all, but the browser doesn’t know that. The browser sends a request to the attacking server with the cookie in the query string.
After we save the comment, the script is executed on the subsequent page where the comment is displayed, as shown in figure 11.2.
We can’t see anything strange here, but the nefarious script is in the HTML source, and the relevant section is shown in listing 11.7.
A long comment <script>document.write(
Of course, the browser dutifully responds to this script and sends the cookie to the attacking site. When we reload the attacking site, we can see that our attack has been executed, as shown in figure 11.3. The other site received our cookie.
Never trust input. Never, ever, ever expect input to be safe. Whether it’s from a human user or a machine, dangerous input is the root attack vector involved in XSS attacks. We don’t trust it coming in, and we certainly don’t trust it when we render it. That’s the key.
One vulnerability in our example application is that it rendered the submitted script as script to be executed by the browser (as shown in figure 11.2). Instead, we should have HTML-encoded the comment.
HTML encoding transforms text from HTML that’s interpreted by the browser into symbols that the browser will render without interpretation. Instead of our script being parsed and executed, it would’ve simply been displayed as text. In our view, we rendered the comment with this markup: <%= Model.Comment %>, but we could’ve applied a built-in function that encodes HTML: <%= Html.Encode(Model.Comment) %>.
Figure 11.4 shows how a harmless HTML-encoded script would appear.
There’s a new feature in ASP.NET 4 that allows developers to conveniently express HTML-encoded output without using the Html.Encode helper function. Instead of specifying output with <%= "text" %>, we can use <%: "text" %>. For more information, refer to Phil Haack’s blog post, “HTML Encoding Code Blocks with ASP.NET 4” (http://mng.bz/Z3V5).
Although HTML encoding all output makes our application much more secure, hackers are crafty and are constantly discovering new ways to evade encoding. It’s important to also check input to our application.
To craft the vulnerable example, we had to disable protective features in ASP.NET MVC 2. Listing 11.8 demonstrates how input validation was specifically disabled.
public ViewResult Save(CommentInput form)
When set to false, the ValidateInput attribute signals ASP.NET to not validate user input to this action. Without this attribute, validation will happen by default, checking the query string, form, and cookies for a list of malicious content. Without this attribute directing ASP.NET to not validate, users submitting unsafe input will see the exception in figure 11.5.
Input validation can prevent safe input if the application is expecting HTML or other markup. It should be disabled with extreme caution, and you should redouble your efforts to HTML-encode all output.
Chrome 4 and the Firefox extension NoScript provide input validation on the client. They refuse to render any script that was present in the previous request. Although these measures aren’t fail-safe, they’re useful tools users can employ to protect themselves against being victimized by certain web application vulnerabilities like XSS.
It’s not easy to enable XSS in ASP.NET MVC 2, thankfully. But it can be done, and all developers should do everything necessary to prevent this common attack. Next we’ll look at XSRF, another common vulnerability in web apps.
Cross-site request forgery (XSRF) is an attack where an attacking website presents a form to the user that, once submitted, issues a request to a vulnerable web application. The vulnerable web application processes the request normally because the hoodwinked user remains authenticated on the vulnerable site.
In this situation, the vulnerable site has no way of knowing whether the submitted request came from itself, which is normal behavior, or from a third-party site. The fix, included in ASP.NET MVC 2, is to provide a token that secure sites can use to ensure that requests are generated only from pages it controls.
In the example code for this chapter, we’ve included a working XSRF demonstration. Again, there are two sites in the solution: a vulnerable one and the attacker. The vulnerable site accepts a simple form post.
If you imagine the secure commands we issue in the course of a regular day—transferring funds between bank accounts, buying or selling securities, authorizing raises, and so on, it could be profitable for a hacker to formulate a special request on your behalf and have you unknowingly transmit it to a site you’re known to visit.
Our attacking site is shown in figure 11.6. This button just begs to be clicked.
Behind the scenes, in the bowels of the HTML source, another story is told, as shown in listing 11.9.
When the aloof user clicks the button, the form is submitted. Not even the AuthorizeAttribute can save us now; we’re already logged in! Figure 11.7 shows the result.
ValidateAntiForgeryTokenAttribute, when applied to an action, requires that the input be accompanied by a special token that ensures it’s from the responding application only. The attribute must be used in tandem with a special HTML helper that outputs the token in the form in the HTML source.
Listing 11.10 shows the attribute on our vulnerable action.
public ViewResult Save(InputModel form)
Listing 11.11 shows the HTML helper we need in the form.
<form method="post" action="/home/save">
<%= Html.AntiForgeryToken() %>
<%= Html.TextBox("Name") %>
Once the token and the attribute are in place, submissions from the site using both will succeed, but attackers will no longer be able to formulate XSRF attacks. If they try, an exception like the one shown in figure 11.8 appears.
The appropriate time to incorporate ValidateAntiForgeryTokenAttribute on actions that accept form submissions is now. Public-facing websites and intranet sites are vulnerable to XSRF, and this quick task is required to develop a secure application.
In the next section, we’ll look at JSON hijacking, which is another attack that requires developers using ASP.NET MVC 2 to take certain precautions.
JSON (pronounced like the English name, Jason) hijacking is a rare hack similar to XSRF, except it’s targeted to request secure JSON from vulnerable applications. The JSON hijacking process involves several steps:
The solution to this exploit offered by ASP.NET MVC 2 is to only accept requests for JSON data by HTTP POST requests, rather than by GETs. This is baked into and enforced by the standard JsonResult action result that ships with the framework. If we were to request data to be returned by JsonResult with a GET request, we wouldn’t receive the JSON data.
The problem with this approach isn’t technical—this works and it prevents JSON hijacking. But it’s a workaround that’s sometimes unnecessary and can interfere with systems developed using the REST architectural style.
If this approach causes problems, we have additional options. First, we can explicitly enable JSON requests from GETs with the code shown in listing 11.13.
public JsonResult GetInsecureJson()
object data = GetData();
return Json(data, JsonRequestBehavior.AllowGet);
This will allow our action to respond to normal JSON GET requests. Finally, we can scrap JsonResult itself, instead using an action result to return only non-vulnerable, non-array-formatted JSON.
The code in listing 11.14 shows a special action result that wraps vulnerable JSON data in a variable, d.
Using this technique, we can still use GETs to retrieve our JSON data, but the JSON is secure because it’s never just an array—any arrays are wrapped in a d variable. We just must be sure to access values through the d variable .
This unconventional code can be confusing. We recommend using the default behavior of using HTTP POST requests to retrieve JSON data. If that becomes a problem, you can switch to this technique.
No application can ever be totally secure, but in this chapter we looked at several vulnerabilities, and you learned how to protect your ASP.NET MVC 2 applications. We explored using AuthorizeAttribute to enforce authentication and authorization on actions. We discussed cross-site scripting and you learned to never trust user input and to HTML-encode all output. Cross-site request forgeries are neutered when the ValidateAntiForgeryTokenAttribute is used to verify that input is coming from trusted sources. You also saw how ASP.NET MVC 2 helps protect against JSON hijacking and how to explicitly work around the changes to JsonResult.
In the next chapter, we’ll dive into Ajax, using it to create rich user experiences and responsive applications.