Статьи

Понимание проверки запросов в ASP.NET MVC 3

Вступление:

Факт, который вы всегда должны помнить: « никогда не доверяйте пользовательским данным» . Приложение, которое доверяет пользовательским данным, может быть легко уязвимо для атак XSS, XSRF, SQL-инъекций и т. Д. XSS и XSRF — очень опасные атаки. Поэтому для смягчения этих атак ASP.NET ввел проверку запросов в ASP.NET 1.1. Во время проверки запроса ASP.NET выдаст исключение HttpRequestValidationException: «С клиента было обнаружено потенциально опасное значение XXX», если он обнаружил, <с последующим восклицательным знаком (например, <!) или <, за которым следуют буквы от a до z (например, <s) или &, за которыми следует знак фунта (например, & # 123) как часть строки запроса, опубликованная Форма и коллекция файлов cookie. В ASP.NET 4.0 проверка запросов становится расширяемой. Это означает, что вы можете продлить проверку запроса. Также в ASP.NET 4.0 проверка запроса по умолчанию включена перед фазой BeginRequest HTTP-запроса. ASP.NET MVC 3 продвигается на один шаг дальше, делая проверку запроса гранулированной. Это позволяет отключить проверку запросов для некоторых свойств модели, в то же время поддерживая проверку запросов для всех остальных случаев. В этой статье я покажу вам использование проверки запросов в ASP.NET MVC 3. Затем я кратко объясню внутреннюю работу гранулярной проверки запросов.

Описание:

Прежде всего создайте новое приложение ASP.NET MVC 3. Затем создайте простой класс модели под названием MyModel, 

1public class MyModel2{3    public string Prop1 { get;  set; }4 5    public string Prop2 { get; set; }6}

 Затем просто обновите метод действия index следующим образом:

1public ActionResult Index(MyModel p)2{3    return View();4}

 

Теперь просто запустите это приложение. Вы обнаружите, что все работает просто отлично. Теперь просто добавьте эту строку запроса ? Prop1 = <s к URL этого приложения, вы получите исключение HttpRequestValidationException.

Теперь просто украсьте метод действия Index с помощью [ValidateInputAttribute (false)],

1[ValidateInput(false)]2public ActionResult Index(MyModel p)3{4    return View();5}

Запустите это приложение еще раз с той же строкой запроса. Вы обнаружите, что ваше приложение работает без каких-либо необработанных исключений.

До сих пор в ASP.NET MVC 3 не было ничего нового, поскольку ValidateInputAttribute присутствовал в предыдущих версиях ASP.NET MVC. Есть проблемы с этим подходом? Да, есть проблема с этим подходом. Проблема в том, что теперь пользователи могут отправлять html как для свойств Prop1, так и для Prop2, и многие разработчики не знают об этом. Это означает, что теперь каждый может отправлять html с обоими параметрами (например ,? Prop1 = <s & Prop2 = <s ). Таким образом, атрибут ValidateInput не дает вам гарантии того, что ваше приложение безопасно для XSS или XSRF. По этой причине команда ASP.NET MVC представила детальную проверку запросов в ASP.NET MVC 3. Давайте посмотрим на эту функцию.

Удалите [ValidateInputAttribute (false)] для действия Index и обновите класс MyModel следующим образом.

1public class MyModel2{3    [AllowHtml]4    public string Prop1 { get;  set; }5 6    public string Prop2 { get; set; }7}

Обратите внимание, что атрибут AllowHtml оформляется только в свойстве Prop1. Запустите это приложение еще раз со строкой запроса ? Prop1 = <s . Вы обнаружите, что ваше приложение работает нормально. Запустите это приложение еще раз со строкой запроса? Prop1 = <s & Prop2 = <s , вы получите исключение HttpRequestValidationException. Это показывает, что детальная проверка запроса в ASP.NET MVC 3 позволяет пользователям отправлять html только для свойств, оформленных с помощью атрибута AllowHtml. 

Иногда вам может понадобиться прямой доступ к Request.QueryString или Request.Form. Вы можете изменить свой код следующим образом,

1[ValidateInput(false)]2public ActionResult Index()3{4    var prop1 = Request.QueryString["Prop1"];5    return View();6}

Запустите это приложение еще раз, вы снова получите исключение HttpRequestValidationException, даже если у вас есть [ValidateInput (false)] в вашем действии Index. Причина в том, что флаги запроса по-прежнему не установлены как недействительные. Я объясню это позже. Для выполнения этой работы вам нужно использовать метод Unvalidated extension,

1public ActionResult Index()2{3    var q = Request.Unvalidated().QueryString;4    var prop1 = q["Prop1"];5    return View();6}

Неподтвержденный метод расширения определен в пространстве имен System.Web.Helpers. Так что вам нужно добавить с помощью System.Web.Helpers; в этом файле класса. Запустите это приложение еще раз, ваше приложение работает нормально.  

Там у вас есть это. Если вам не интересно знать, как работает проверка гранулярных запросов, вы можете полностью пропустить следующие абзацы. Если вам интересно, продолжайте читать.  

Создайте новое приложение ASP.NET MVC 2, затем откройте файл global.asax.cs и следующие строки: 

1protected void Application_BeginRequest()2{3    var q = Request.QueryString;4}

Затем сделайте метод действия Index как,

1[ValidateInput(false)]2public ActionResult Index(string id)3{4    return View();5}

Обратите внимание, что метод действия Index содержит параметр, и этот метод действия имеет значение [ValidateInput (false)]. Запустите это приложение еще раз, но теперь со строкой запроса ? Id = <s вы получите исключение HttpRequestValidationException в методе Application_BeginRequest. Теперь просто добавьте следующую запись в web.config,

1<httpRuntime requestValidationMode="2.0"/>

 Теперь запустите это приложение снова. На этот раз ваше приложение будет работать нормально. Теперь просто посмотрите на следующую цитату из ASP.NET 4: критические изменения :

«В ASP.NET 4 по умолчанию проверка запросов включена для всех запросов, потому что она включена до фазы BeginRequest HTTP-запроса. В результате проверка запросов применяется ко всем запросам всех ресурсов ASP.NET, а не только. запросы страницы aspx. Сюда входят такие запросы, как вызовы веб-службы и пользовательские обработчики HTTP. Проверка запроса также активна, когда пользовательские модули HTTP считывают содержимое HTTP-запроса. «

Это ясно говорит о том, что проверка запроса включена до фазы BeginRequest HTTP-запроса. Чтобы понять, что означает «включен», нам необходимо увидеть методы / свойства HttpRequest.ValidateInput, HttpRequest.QueryString и HttpRequest.Form в сборке System.Web. Вот реализация методов / свойств HttpRequest.ValidateInput, HttpRequest.QueryString и HttpRequest.Form в сборке System.Web,

 

01public NameValueCollection Form02{03    get04    {05        if (this._form == null)06        {07            this._form = new HttpValueCollection();08            if (this._wr != null)09            {10                this.FillInFormCollection();11            }12            this._form.MakeReadOnly();13        }14        if (this._flags[2])15        {16            this._flags.Clear(2);17            this.ValidateNameValueCollection(this._form, RequestValidationSource.Form);18        }19        return this._form;20    }21}22 23public NameValueCollection QueryString24{25    get26    {27        if (this._queryString == null)28        {29            this._queryString = new HttpValueCollection();30            if (this._wr != null)31            {32                this.FillInQueryStringCollection();33            }34            this._queryString.MakeReadOnly();35        }36        if (this._flags[1])37        {38            this._flags.Clear(1);39            this.ValidateNameValueCollection(this._queryString, RequestValidationSource.QueryString);40        }41        return this._queryString;42    }43}44 45public void ValidateInput()46{47    if (!this._flags[0x8000])48    {49        this._flags.Set(0x8000);50        this._flags.Set(1);51        this._flags.Set(2);52        this._flags.Set(4);53        this._flags.Set(0x40);54        this._flags.Set(0x80);55        this._flags.Set(0x100);56        this._flags.Set(0x200);57        this._flags.Set(8);58    }59}

Приведенный выше код указывает, что HttpRequest.QueryString и HttpRequest.Form будут проверять строку запроса и коллекцию форм, только если установлены определенные флаги. Эти флаги устанавливаются автоматически, если вы вызываете метод HttpRequest.ValidateInput. Теперь снова запустите указанное выше приложение (не забудьте добавить строку запроса ? Id = <s в URL) с теми же настройками (т. Е. Настройкой requestValidationMode = «2.0» в web.config и методом Application_BeginRequest в global.asax.cs. ), ваше приложение будет работать нормально. Теперь просто обновите метод Application_BeginRequest как,

1protected void Application_BeginRequest()2{3    Request.ValidateInput();4    var q = Request.QueryString;5}

Обратите внимание, что я вызываю метод Request.ValidateInput до использования свойства Request.QueryString. Метод ValidateInput будет внутренне устанавливать определенные флаги (обсуждалось выше). Затем эти флаги сообщают свойству Request.QueryString (и Request.Form), которое проверяет строку (или форму) запроса, когда пользователь вызывает свойство Request.QueryString (или Request.Form). Итак, снова запустив это приложение с ? Id = <sСтрока запроса вызовет исключение HttpRequestValidationException. Теперь я надеюсь, что вам понятно, что делает requestValidationMode. Он просто сообщает ASP.NET, что не следует вызывать метод Request.ValidateInput для внутреннего использования перед фазой BeginRequest HTTP-запроса, если для requestValidationMode установлено значение меньше 4.0 в web.config. Вот реализация метода HttpRequest.ValidateInputIfRequiredByConfig, который докажет это утверждение (не путайте с HttpRequest и Request. Request является свойством класса HttpRequest),

01internal void ValidateInputIfRequiredByConfig()02{03    ...............................................................04    ...............................................................05    ...............................................................06    ...............................................................07    if (httpRuntime.RequestValidationMode >= VersionUtil.Framework40)08    {09        this.ValidateInput();10    }11}

Надеемся, что приведенное выше обсуждение прояснит вам, как requestValidationMode работает в ASP.NET 4. Также интересно отметить, что и HttpRequest.QueryString, и HttpRequest.Form выдают исключение только при первом обращении к ним. Любой последующий доступ к HttpRequest.QueryString и HttpRequest.Form не вызовет никаких исключений. Продолжая приведенный выше пример, просто обновите метод Application_BeginRequest в файле global.asax.cs следующим образом:

01protected void Application_BeginRequest()02{03    try04    {05        var q = Request.QueryString;06        var f = Request.Form;07    }08    catch//swallow this exception09    {10    }11    var q1 = Request.QueryString;12    var f1 = Request.Form;13}

Без установки requestValidationMode равным 2.0 и без декорирования атрибута ValidateInput для действия Index ваше приложение будет работать нормально, поскольку HttpRequest.QueryString и HttpRequest.Form очистят свои флаги после прочтения HttpRequest.QueryString и HttpRequest.Form в первый раз (см. Реализацию HttpRequest.QueryString и HttpRequest.Form выше).

Теперь давайте посмотрим внутреннюю работу проверки гранулярных запросов ASP.NET MVC 3. Прежде всего нам нужно увидеть тип свойств HttpRequest.QueryString и HttpRequest.Form. Свойства HttpRequest.QueryString и HttpRequest.Form имеют тип NameValueCollection, который унаследован от класса NameObjectCollectionBase. Класс NameObjectCollectionBase содержит поля _entriesArray, _entriesTable, NameObjectEntry.Key и NameObjectEntry.Value, которые внутренняя проверка гранулярных запросов использует. Кроме того, гранулярная проверка запросов также использует поля _queryString, _form и _flags, метод ValidateString и индексатор класса HttpRequest. Давайте посмотрим, когда и как гранулированная проверка запросов использует эти поля.

Создайте новое приложение ASP.NET MVC 3. Затем установите точку останова в методе Application_BeginRequest, а другую точку останова в методе HomeController.Index. Теперь просто запустите это приложение. При достижении точки останова в методе Application_BeginRequest добавьте следующее выражение в окне быстрого просмотра System.Web.HttpContext.Current.Request.QueryString.  Вы увидите следующий экран,    

Теперь нажмите F5, чтобы вторая точка останова внутри метода HomeController.Index достигла цели. При достижении второй точки останова снова добавьте следующее выражение в окне быстрого просмотра System.Web.HttpContext.Current.Request.QueryString.  Вы увидите следующий экран,

Первый экран показывает, что поле _entriesTable имеет тип System.Collections.Hashtable, а поле _entriesArray имеет тип System.Collections.ArrayList на этапе BeginRequest HTTP-запроса. В то время как второй экран показывает, что тип _entriesTable изменяется на Microsoft.Web.Infrastructure.DynamicValidationHelper.LazilyValidatingHashtable, а тип _entriesArray изменяется на Microsoft.Web.Infrastructure.DynamicValidationHelper.LazilyValidatingArrayList во время выполнения метода действия Index. В дополнение к этим членам ASP.NET MVC 3 также выполняет некоторые операции над _flags, _form, _queryString и другими членами класса HttpRuntime внутри. Это показывает, что ASP.NET MVC 3 выполняет некоторую операцию с членами класса HttpRequest для обеспечения возможности детальной проверки запроса.

Классы LazilyValidatingArrayList и LazilyValidatingHashtable определены в сборке Microsoft.Web.Infrastructure. Вы можете удивиться, почему их имя начинается с Lazily . Дело в том, что теперь с ASP.NET MVC 3 проверка запросов будет выполняться лениво. Проще говоря, сборка Microsoft.Web.Infrastructure теперь берет на себя ответственность за проверку запросов от сборки System.Web. Смотрите ниже экранов. Первый экран, показывающий исключение HttpRequestValidationException в приложении ASP.NET MVC 2, а второй экран, показывающий исключение HttpRequestValidationException в приложении ASP.NET MVC 3.

В MVC 2:


В MVC 3:

Трассировка стека второго снимка экрана показывает, что сборка Microsoft.Web.Infrastructure (вместо сборки System.Web) теперь выполняет проверку запросов в ASP.NET MVC 3. Теперь вы можете спросить: где сборка Microsoft.Web.Infrastructure выполняет некоторые работа над членами класса HttpRequest. Есть по крайней мере два места , где Microsoft.Web.Infrastructure узел выполнения некоторых операций, Microsoft.Web.Infrastructure.DynamicValidationHelper.GranularValidationReflectionUtil.GetInstance метод и Microsoft.Web.Infrastructure.DynamicValidationHelper.ValidationUtility.CollectionReplacer.ReplaceCollection метод, Вот реализация из этих методов,

001private static GranularValidationReflectionUtil GetInstance()002{003    try004    {005        if (DynamicValidationShimReflectionUtil.Instance != null)006        {007            return null;008        }009        GranularValidationReflectionUtil util = new GranularValidationReflectionUtil();010        Type containingType = typeof(NameObjectCollectionBase);011        string fieldName = "_entriesArray";012        bool isStatic = false;013        Type fieldType = typeof(ArrayList);014        FieldInfo fieldInfo = CommonReflectionUtil.FindField(containingType, fieldName, isStatic, fieldType);015        util._del_get_NameObjectCollectionBase_entriesArray = MakeFieldGetterFunc<NameObjectCollectionBase, ArrayList>(fieldInfo);016        util._del_set_NameObjectCollectionBase_entriesArray = MakeFieldSetterFunc<NameObjectCollectionBase, ArrayList>(fieldInfo);017        Type type6 = typeof(NameObjectCollectionBase);018        string str2 = "_entriesTable";019        bool flag2 = false;020        Type type7 = typeof(Hashtable);021        FieldInfo info2 = CommonReflectionUtil.FindField(type6, str2, flag2, type7);022        util._del_get_NameObjectCollectionBase_entriesTable = MakeFieldGetterFunc<NameObjectCollectionBase, Hashtable>(info2);023        util._del_set_NameObjectCollectionBase_entriesTable = MakeFieldSetterFunc<NameObjectCollectionBase, Hashtable>(info2);024        Type targetType = CommonAssemblies.System.GetType("System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry");025        Type type8 = targetType;026        string str3 = "Key";027        bool flag3 = false;028        Type type9 = typeof(string);029        FieldInfo info3 = CommonReflectionUtil.FindField(type8, str3, flag3, type9);030        util._del_get_NameObjectEntry_Key = MakeFieldGetterFunc<string>(targetType, info3);031        Type type10 = targetType;032        string str4 = "Value";033        bool flag4 = false;034        Type type11 = typeof(object);035        FieldInfo info4 = CommonReflectionUtil.FindField(type10, str4, flag4, type11);036        util._del_get_NameObjectEntry_Value = MakeFieldGetterFunc<object>(targetType, info4);037        util._del_set_NameObjectEntry_Value = MakeFieldSetterFunc(targetType, info4);038        Type type12 = typeof(HttpRequest);039        string methodName = "ValidateString";040        bool flag5 = false;041        Type[] argumentTypes = new Type[] { typeof(string), typeof(string), typeof(RequestValidationSource) };042        Type returnType = typeof(void);043        MethodInfo methodInfo = CommonReflectionUtil.FindMethod(type12, methodName, flag5, argumentTypes, returnType);044        util._del_validateStringCallback = CommonReflectionUtil.MakeFastCreateDelegate<HttpRequest, ValidateStringCallback>(methodInfo);045        Type type = CommonAssemblies.SystemWeb.GetType("System.Web.HttpValueCollection");046        util._del_HttpValueCollection_ctor = CommonReflectionUtil.MakeFastNewObject<Func<NameValueCollection>>(type);047        Type type14 = typeof(HttpRequest);048        string str6 = "_form";049        bool flag6 = false;050        Type type15 = type;051        FieldInfo info6 = CommonReflectionUtil.FindField(type14, str6, flag6, type15);052        util._del_get_HttpRequest_form = MakeFieldGetterFunc<HttpRequest, NameValueCollection>(info6);053        util._del_set_HttpRequest_form = MakeFieldSetterFunc(typeof(HttpRequest), info6);054        Type type16 = typeof(HttpRequest);055        string str7 = "_queryString";056        bool flag7 = false;057        Type type17 = type;058        FieldInfo info7 = CommonReflectionUtil.FindField(type16, str7, flag7, type17);059        util._del_get_HttpRequest_queryString = MakeFieldGetterFunc<HttpRequest, NameValueCollection>(info7);060        util._del_set_HttpRequest_queryString = MakeFieldSetterFunc(typeof(HttpRequest), info7);061        Type type3 = CommonAssemblies.SystemWeb.GetType("System.Web.Util.SimpleBitVector32");062        Type type18 = typeof(HttpRequest);063        string str8 = "_flags";064        bool flag8 = false;065        Type type19 = type3;066        FieldInfo flagsFieldInfo = CommonReflectionUtil.FindField(type18, str8, flag8, type19);067        Type type20 = type3;068        string str9 = "get_Item";069        bool flag9 = false;070        Type[] typeArray4 = new Type[] { typeof(int) };071        Type type21 = typeof(bool);072        MethodInfo itemGetter = CommonReflectionUtil.FindMethod(type20, str9, flag9, typeArray4, type21);073        Type type22 = type3;074        string str10 = "set_Item";075        bool flag10 = false;076        Type[] typeArray6 = new Type[] { typeof(int), typeof(bool) };077        Type type23 = typeof(void);078        MethodInfo itemSetter = CommonReflectionUtil.FindMethod(type22, str10, flag10, typeArray6, type23);079        MakeRequestValidationFlagsAccessors(flagsFieldInfo, itemGetter, itemSetter, out util._del_BitVector32_get_Item, out util._del_BitVector32_set_Item);080        return util;081    }082    catch083    {084        return null;085    }086}087 088private static void ReplaceCollection(HttpContext context, FieldAccessor<NameValueCollection> fieldAccessor, Func<NameValueCollection> propertyAccessor, Action<NameValueCollection> storeInUnvalidatedCollection, RequestValidationSource validationSource, ValidationSourceFlag validationSourceFlag)089{090    NameValueCollection originalBackingCollection;091    ValidateStringCallback validateString;092    SimpleValidateStringCallback simpleValidateString;093    Func<NameValueCollection> getActualCollection;094    Action<NameValueCollection> makeCollectionLazy;095    HttpRequest request = context.Request;096    Func<bool> getValidationFlag = delegate {097        return _reflectionUtil.GetRequestValidationFlag(request, validationSourceFlag);098    };099    Func<bool> func = delegate {100        return !getValidationFlag();101    };102    Action<bool> setValidationFlag = delegate (bool value) {103        _reflectionUtil.SetRequestValidationFlag(request, validationSourceFlag, value);104    };105    if ((fieldAccessor.Value != null) && func())106    {107        storeInUnvalidatedCollection(fieldAccessor.Value);108    }109    else110    {111        originalBackingCollection = fieldAccessor.Value;112        validateString = _reflectionUtil.MakeValidateStringCallback(context.Request);113        simpleValidateString = delegate (string value, string key) {114            if (((key == null) || !key.StartsWith("__", StringComparison.Ordinal)) && !string.IsNullOrEmpty(value))115            {116                validateString(value, key, validationSource);117            }118        };119        getActualCollection = delegate {120            fieldAccessor.Value = originalBackingCollection;121            bool flag = getValidationFlag();122            setValidationFlag(false);123            NameValueCollection col = propertyAccessor();124            setValidationFlag(flag);125            storeInUnvalidatedCollection(new NameValueCollection(col));126            return col;127        };128        makeCollectionLazy = delegate (NameValueCollection col) {129            simpleValidateString(col[null], null);130            LazilyValidatingArrayList array = new LazilyValidatingArrayList(_reflectionUtil.GetNameObjectCollectionEntriesArray(col), simpleValidateString);131            _reflectionUtil.SetNameObjectCollectionEntriesArray(col, array);132            LazilyValidatingHashtable table = new LazilyValidatingHashtable(_reflectionUtil.GetNameObjectCollectionEntriesTable(col), simpleValidateString);133            _reflectionUtil.SetNameObjectCollectionEntriesTable(col, table);134        };135        Func<bool> hasValidationFired = func;136        Action disableValidation = delegate {137            setValidationFlag(false);138        };139        Func<int> fillInActualFormContents = delegate {140            NameValueCollection values = getActualCollection();141            makeCollectionLazy(values);142            return values.Count;143        };144        DeferredCountArrayList list = new DeferredCountArrayList(hasValidationFired, disableValidation, fillInActualFormContents);145        NameValueCollection target = _reflectionUtil.NewHttpValueCollection();146        _reflectionUtil.SetNameObjectCollectionEntriesArray(target, list);147        fieldAccessor.Value = target;148    }149}

Hopefully the above code will help you to understand the internal working of granular request validation. It is also important to note that Microsoft.Web.Infrastructure assembly invokes HttpRequest.ValidateInput method internally. For further understanding please see Microsoft.Web.Infrastructure assembly code. Finally you may ask: at which stage ASP NET MVC 3 will invoke these methods. You will find this answer by looking at the following method source,

  • Unvalidated extension method for HttpRequest class defined in System.Web.Helpers.Validation class.
  • System.Web.Mvc.MvcHandler.ProcessRequestInit method.
  • System.Web.Mvc.ControllerActionInvoker.ValidateRequest method.
  • System.Web.WebPages.WebPageHttpHandler.ProcessRequestInternal method.

Summary:

ASP.NET helps in preventing XSS attack using a feature called request validation. In this article, I showed you how you can use granular request validation in ASP.NET MVC 3. I explain you the internal working of  granular request validation. Hope you will enjoy this article too.