Вступление:
Факт, который вы всегда должны помнить: « никогда не доверяйте пользовательским данным» . Приложение, которое доверяет пользовательским данным, может быть легко уязвимо для атак 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.