Security Issue in ASP.NET MVC3 JsonValueProviderFactory
Introduction:
Model binding(a mechanism for mapping action method parameters with request data), is one of the most popular feature of ASP.NET MVC. The default model binder get its data from different value providers. In ASP.NET MVC 2, by default, these value providers include FormValueProvider, QueryStringValueProvider, RouteDataValueProvider and HttpFileCollectionValueProvider. ASP.NET MVC 3 added two additional value providers, ChildActionValueProvider and a value provider for Json. Json value provider makes it very easy to model bind your action method parameters with incoming Json data, but I have found a security issue with this value provider. In this article, I will show you the security issue regarding Json value provider and also show you how to protect against this security threat.
Description:
To see this issue, let's create a sample ASP.NET MVC 3 application which include a blog post with comments. For making this application, you need to create a Comment model,
public class Comment { [Required] public string Name { get; set; } [Required] public string UserComment { get; set; } }
Then you need to store the user comments somewhere. For this sample application, you can store the user comments in a static, non thread safe generic list. Here is the definition of this generic list,
public static class Blog { public static List<Comment> Comments = new List<Comment>(); }
Then just open(or create) HomeController.cs file and add the following code,
public class HomeController : Controller { public ActionResult BlogPostWithComments() { ViewData["Comments"] = Blog.Comments; return View(); } [HttpPost] public ActionResult BlogPostWithComments(Comment c) { if (ModelState.IsValid) { Blog.Comments.Add(new Comment { UserComment = c.UserComment, Name = c.Name }); return RedirectToAction("BlogPostWithComments"); } ViewData["Comments"] = Blog.Comments; return View(); } }
Next, add a view for this action,
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SecurityIssueInJsonValueProviderFactory.Models.Comment>" %> <%@ Import Namespace="SecurityIssueInJsonValueProviderFactory.Models" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> A Sample Blog Post </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <script src="<%= Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script> <script src="<%= Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script> <h2> A Sample Blog Post</h2> <br /> <br /> This is just a sample blog post. <br /> <br /> <br /> <br /> <h3 style="text-decoration: underline;"> Comments:</h3> <br /> <% using (Html.BeginForm()) { %> <%= Html.ValidationSummary(true) %> <fieldset> <legend>Comment</legend> <div class="editor-label"> <%= Html.LabelFor(model => model.Name) %> </div> <div class="editor-field"> <%= Html.TextBoxFor(model => model.Name) %> <%= Html.ValidationMessageFor(model => model.Name) %> </div> <div class="editor-label"> <%= Html.LabelFor(model => model.UserComment) %> </div> <div class="editor-field"> <%= Html.TextAreaFor(model => model.UserComment) %> <%= Html.ValidationMessageFor(model => model.UserComment) %> </div> <p> <input type="submit" value="Save" /> </p> </fieldset> <% } %><br /> <table> <% foreach (var item in ViewData["Comments"] as List<Comment>) { %> <tr> <td> <strong>Name:</strong> <%= item.Name %> </td> </tr> <tr> <td> <strong>Comment:</strong> <%= item.UserComment %> </td> </tr> <tr> <td> <br /> </td> </tr> <% } %> </table> </asp:Content>
Now just run this application. You will find that everything is working perfectly. When malicious user try to enter any input which contains HTML tags, they will get a HttpRequestValidationException exception. Now let's see the security issue.
Open the fiddler tool, then build a Json request from fiddler and then execute this request as shown in the figure below,
Now just run your application again, you will see the following screen,
This shows that an attacker can easily bypass the ASP.NET request validation if you are using JsonValueProviderFactory. Therefore, if you are not using Json requests to bind your action method parameters, then I recommend to remove Json value provider,
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories[2]);
But if your application is using Json value provider then you can mitigate this issue by creating a custom default model binder by creating a class that inherits from DefaultModelBinder,
public class MyDefaultModelBinder : DefaultModelBinder { protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { if (value is string && (controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))) { if (controllerContext.Controller.ValidateRequest && bindingContext.PropertyMetadata[propertyDescriptor.Name].RequestValidationEnabled) { int index; if (IsDangerousString(value.ToString(), out index)) { throw new HttpRequestValidationException("Dangerous Input Detected"); } } } return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value); } private static char[] startingChars = new char[] { '<', '&' }; private static bool IsAtoZ(char c) { return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))); } static bool IsDangerousString(string s, out int matchIndex) { matchIndex = 0; int startIndex = 0; while (true) { int num2 = s.IndexOfAny(startingChars, startIndex); if (num2 < 0) { return false; } if (num2 == (s.Length - 1)) { return false; } matchIndex = num2; char ch = s[num2]; if (ch != '&') { if ((ch == '<') && ((IsAtoZ(s[num2 + 1]) || (s[num2 + 1] == '!')) || ((s[num2 + 1] == '/') || (s[num2 + 1] == '?')))) { return true; } } else if (s[num2 + 1] == '#') { return true; } startIndex = num2 + 1; } } }
The above code first check that whether this is a Json request, if request is json then it check whether the request validation is enabled for the current property, if request validation is enabled then it check whether the provided value of the current property is dangerous using IsDangerousString method. If the value is dangerous, then it will throw a HttpRequestValidationException exception. Note that the IsDangerousString method is the same method used by the default request validator. Finally you need to register this default model binder in global.asax.cs file,
ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder();
Now with the help of this model binder your application will throw HttpRequestValidationException exception in Json requests if malicious user send some dangerous input.
Summary:
In this article, I showed you the security issue regarding JsonValueProviderFactory in ASP.NET MVC 3. I also showed you how you can protect your application against this security issue. I am also attaching a sample application, so that you can test this issue by yourself. Hopefully you will enjoy this article too.