Preventing Javascript Encoding XSS attacks in ASP.NET MVC
I just posted about cross-site scripting, or XSS attacks, in ASP.NET - take a quick look at that post for some background on XSS attacks. I wanted to take a deeper look at Javascript Encoding XSS attacks. They're a particularly tricky form of XSS, since Javascript encoded values are valid HTML and will pass through default HTML encoding. Here's an example - let's assume we want to add a special welcome message to our home page if a UserName parameter is present so we can send out personalized links to the site in an e-mail promotion. We start by modifying the HomeController / Index method:
public ActionResult Index(string UserName) { ViewBag.UserName = UserName; return View(); }
Then we add this information to the home index view, using Javascript so that we can make sure our users notice it:
Warning: Do not use this code - it's got an XSS vulnerability.
@{ ViewBag.Title = "Home Page"; } <h2 id="welcome-message">Welcome to our website</h2> @if(!string.IsNullOrWhiteSpace(ViewBag.UserName)) { <script type="text/javascript"> $(function () { var message = 'Welcome, @ViewBag.UserName!'; $("#welcome-message").html(message).hide().show('slow'); }); </script> }
Sadly, we've just exposed our end users to an XSS vulnerability. Nonsense, you say! We tested that with the following url: http://localhost:58570/?UserName=<script>alert('pwnd')</script>
As you can see, it was detected by request validation:
But since this value is being rendered via Javascript, it's vulnerable to Javascript encoding, which won't be picked up by the ASP.NET encoder. Try this url: http://localhost:58570/?UserName=Jon\x3cscript\x3e%20alert(\x27pwnd\x27)%20\x3c/script\x3e
Note: Remember that we're using an alert here for demonstration purposes, but a real XSS attack will do something more sinister, designed so end users will never notice.
Fixing the Javascript encoding XSS vulnerability
There are two ways to handle this. The simplest is to use the @Ajax.JavaScriptStringEncode helper function, like this:
@{ ViewBag.Title = "Home Page"; } <h2 id="welcome-message">Welcome to our website</h2> @if(!string.IsNullOrWhiteSpace(ViewBag.UserName)) { <script type="text/javascript"> $(function () { var message = 'Welcome, @Ajax.JavaScriptStringEncode(ViewBag.UserName)!'; $("#welcome-message").html(message).hide().show('slow'); }); </script> }
If we've included the AntiXSS library in our project, we can bring in the namespace with a @using Microsoft.Security.Application statement and call into the AntiXSS library's JavaScriptStringEncode function, which follows a whitelist approach to screen out alternate encodings and character sets.
@using Microsoft.Security.Application @{ ViewBag.Title = "Home Page"; } <h2 id="welcome-message">Welcome to our website</h2> @if(!string.IsNullOrWhiteSpace(ViewBag.UserName)) { <script type="text/javascript"> $(function () { var message = 'Welcome, @Encoder.JavaScriptEncode(ViewBag.UserName, false)!'; $("#welcome-message").html(message).hide().show('slow'); }); </script> }
Note: By default, the AntiXSS JavaScriptEncode function wraps the value in single quotes. With AntiXSS 4.1, there's an optional second parameter which allows turning that behavior off by passing in false, as shown above.
With either of the above two checks in place, the Javascript XSS injection is caught: