Search code examples
c#asp.netjsonrequest-validation

ASP.Net RequestValidation for json POST to match Form RequestValidation


I want to be able to do RequestValidation on content-type "application/json" in a consistent manner to Form validation ("application/x-www-form-urlencoded").

I've read https://msdn.microsoft.com/en-us/library/system.web.util.requestvalidator(v=vs.110).aspx but it hasn't been too much help.

I'm looking at trying to implement a custom request validator to replace the default System.Web.Util.RequestValidator.

I've also looked at https://msdn.microsoft.com/en-us/library/system.web.util.requestvalidator(v=vs.110).aspx

If I can't get the input to be consistently validated I will probably remove the ability to send json to the controllers and force everything through forms - not ideal as I'd like some methods to accept json.

I already encode the output (where possible) but I still want defense in depth by validating the input and ideally consistently for forms and json.

Unfortunately a lot of the MSDN docs on this are heavily out of date and now irrelevant.

I'm using .net 4.6.1 for reference.

Is it sensible to try and validate requests in this manner?


Solution

  • In the end I've implemented a custom ModelBinder...this was a combination of reading this https://weblogs.asp.net/imranbaloch/security-issue-in-asp-net-mvc3-jsonvalueproviderfactory and this https://gist.github.com/jamescrowley/b8c0c006e7b00e28cbbf

    It modifies the source of the request (RequestValidationSource.Form) to make it look like a form so it can be validated in the same pipeline.

    public class JsonValidatingModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var result = base.BindModel(controllerContext, bindingContext);
            if (!IsJsonRequest(controllerContext))
            {
                return result;
            }
            if (!bindingContext.ModelMetadata.RequestValidationEnabled)
            {
                return result;
            }
            if (result != null)
            {
                EnsureRequestFieldIsValid(controllerContext, result);
            }
            return result;
        }
    
        static void EnsureRequestFieldIsValid(ControllerContext controllerContext, object result)
        {
            int index;
            // abusing RequestValidationSource enum
            if (!RequestValidator.Current.InvokeIsValidRequestString(
                controllerContext.HttpContext.ApplicationInstance.Context,
                result.ToString(), RequestValidationSource.Form, null, out index))
            {
                throw new HttpRequestValidationException(
                    "A potentially dangerous value was detected from the client ");
            }
        }
    
        static bool IsJsonRequest(ControllerContext controllerContext)
        {
            return controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
        }
    

    and in the Global.asax...

        protected void Application_Start()
        {
            System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new JsonValidatingModelBinder();
    
        }