Asp.net Mvc中利用ValidationAttribute實現xss過濾


        在網站開發中,需要注意的一個問題就是防范XSS攻擊,Asp.net mvc中已經自動為我們提供了這個功能。用戶提交數據時時,在生成Action參數的過程中asp.net會對用戶提交的數據進行驗證,一旦發現提交的數據中包含了XSS攻擊的代碼,就會拋出異常,用戶在這時候就會看到一個出錯頁面。這種默認的行為保證了網站的安全性,但是對於用戶體驗來說卻不夠友好,所以大多數人都希望對用戶進行提示,或者對提交的數據進行過濾,移除掉XSS攻擊的代碼。

   對於此類問題,網上有很多人問過,通過百度搜索出來的解決方法好多都只提到了“關閉頁面數據驗證”。確實,關閉了頁面數據驗證后,用戶提交的任何數據都會到達服務器端的處理程序,在asp.net mvc中這一點可以通過在model的相應屬性上附加AllowHtmlAttribute或者在Action上附加ValidateInputAttribute(false)來實現。但是比關閉頁面數據驗證更重要的一點是,關閉之后,這個數據驗證和處理的重擔就要由程序員來承擔了

         解決這個問題最直接的方法就是在每一個要處理提交數據的Action的開始,對相應的參數進行過濾,對於XSS攻擊代碼的過濾,可以使用微軟發布的名為AntiXss的類庫,通過Nuget可以獲取該類庫,在我的解決方法中也是使用此類庫進行過濾的。

      我新建了一個Asp.net mvc項目進行演示,只有一個Controller名字為PersonController,一個Model,名字為PersonModel,PersonController中只有兩個Action,全部代碼如下。

public class PersonModel
{
     [AllowHtml]
     //別忘了AllowHtmlAttribute,要不然提交數據就報錯了
      public string Name { get; set; }

     public int Age { get; set; }
}

public class PersonController : Controller
{
     public ActionResult Index()
     {
         return View();
     }

     public ActionResult Save(PersonModel model)
     {
         //Sanitizer為AntiXss類庫提供的靜態類,用於過濾XSS代碼
          model.Name = Sanitizer.GetSafeHtmlFragment(model.Name);
         //保存到數據庫中
          return Content("Success");
     }
}

視圖文件Index.cshtml內容如下

@model AntiXss.Models.PersonModel
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
@using (Html.BeginForm("Save","Person",FormMethod.Post))
{
    @Html.LabelFor(model=>model.Name);
    @Html.EditorFor(model=>model.Name);
    <br/>
    @Html.LabelFor(model=>model.Age)
    @Html.EditorFor(model=>model.Age)
    
    <input type="submit" value="submit"/>
}

        這樣的代碼無疑是可以達到我們過來XSS攻擊的目的的,但是在實際項目中,Controller往往有數十個,Action的數目更是成百上千,而且ViewModel的屬性又往往很多,如果我們按照上面的方式逐個Action的逐個Model的屬性進行處理,代碼會變得又臭又長,而且還容易遺漏。使用這種方式來進行過濾實在是一種自虐行為呀。

       優秀的程序員都是懶漢,對於這種繁瑣的體力勞動,一定要想方設法地避免。在asp.net mvc中,給我們提供了很多工具以實現aop編程,最常用的就是各種Filter了,所以在解決此問題時,我就想是否可以利用asp.net mvc提供的aop編程來實現XSS過濾,經過思考和翻閱蔣金楠的《ASP.NET MVC4框架揭秘》,最終找到了一種較好的解決方式,就是通過ValidationAttribute來實現XSS攻擊代碼過濾。

      ValidationAttribute是所有驗證屬性的基類,RangeAttribute, RequiredAttribute, StringLengthAttribute都是它的子類,這個類的中包有一個名為IsValid的方法,來對數據進行驗證,方法聲明如下:

protected virtual ValidationResult IsValid(Object value, ValidationContext validationContext)

 

        參數value即為要驗證的對象,參數ValidationContext為驗證上下文,此類包含了較多的信息,比較重要的有屬性ObjectInstance和MemberName

其中ValidationContext的ObjectInstance屬性可獲取要驗證的對象,而MemberName可獲取或設置要驗證的成員名稱。這里要進行一下解釋,按照我上面的說法,value是要驗證的對象,ValidationContext.ObjectInstance也是要驗證的對象,難道它們二者是同一個對象么,答案是No,(不是我故意要把他們表達成一個意思,而是MSDN太坑,本段開頭摘自MSDN),對於我們示例中的PersonModel類型來說,由於其是一個復雜類型,所以最終的驗證會落到它的各個屬性上,假如要驗證屬性Name,參數value即為屬性Name的值,而ValidationContext.ObjectInstance則為一個PersonModel的實例,ValidationContext.MemberName的值按照MSDN的解釋,應該是一個字符串“Name”;這下大家清楚二者的區別了吧。我之所以說假如要驗證屬性Name,是因為屬性Name上現在還沒有任何的驗證特性(AllowHtmlAttribute不是一個驗證特性)。

        到這里我想可能有的人已經想到我要怎么做了,在這里我獲得了屬性值value,也獲得了包含該屬性的實例ValidationContext.ObjectInstance,接下來我要做的就是將該屬性值進行修改就可以了,修改屬性值可以通過反射輕松實現,所以我的用於過濾XSS攻擊代碼的自定義驗證屬性就寫出來了,如下

public class AntiXssAttribute :ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //對於XSS攻擊,只需要對string類型進行驗證就可以了
            var str = value as string;
            if (!string.IsNullOrWhiteSpace(str) && 
                validationContext.ObjectInstance != null && !
                string.IsNullOrWhiteSpace(validationContext.MemberName))
            {
                str = Sanitizer.GetSafeHtmlFragment(str);
                PropertyInfo pi = validationContext.ObjectType.GetProperty(validationContext.MemberName,
                    BindingFlags.Public | BindingFlags.Instance);
                pi.SetValue(validationContext.ObjectInstance,str);
            }
            //由於這個類的目的並不是為了驗證,所以返回驗證成功
            return ValidationResult.Success;
        }
}

       然后我們將這個自定義的驗證特性附加到PersonModel的Name屬性上(一定不要刪除AllowHtmlAttribute,要不然提交包含html標簽或者js代碼的數據時會出錯的),當用戶提交數據時,asp.net在進行model驗證時就會自動為我們過濾XSS攻擊代碼了,一切看起來都是那么的美好,可是事實並非如此!!

       當程序運行時,用戶提交的XSS代碼並沒有被過濾,原因是ValidationContext.MemberName屬性根本不存在,這實在是微軟的一個坑,MSDN告訴我們通過這個屬性可以獲取或設置要驗證的成員名稱,可是其實自始至終根本沒有代碼來設置這個屬性值,這個屬性值一直都是null,所以要想讓我們的代碼順利進行,我們要想辦法給ValidationContext.MemberName賦值才可以,要給ValidationContext的這個屬性賦值,自然要在實例化它的地方。對於ValidationContext對象的實例化,我在這里不贅述,因為這涉及到Asp.net mvc的模型驗證機制,這一點蔣金楠的博文早就講清楚了,而我也自認為不會講的比他更清楚,想了解的人請閱讀蔣金楠的博客ASP.NET MVC以ModelValidator為核心的Model驗證體系: ModelValidator

ASP.NET MVC基於標注特性的Model驗證:DataAnnotationsModelValidator

ASP.NET MVC基於標注特性的Model驗證:DataAnnotationsModelValidatorProvider

        最終我實現了自己的AntiXssDataAnnotationsModelValidator和AntiXssDataAnnotationsModelValidatorProvider,在AntiXssDataAnnotationsModelValidator中實例化了ValidationContext對象,並且為該對象的MemberName屬性賦值。

public class AntiXssDataAnnotationsModelValidator:DataAnnotationsModelValidator
 {
        public AntiXssDataAnnotationsModelValidator(ModelMetadata metadata,ControllerContext context,AntiXssAttribute attribute)
            :base(metadata,context,attribute)
        { }

        public override IEnumerable<ModelValidationResult> Validate(object container)
        {
            var validationContext = new ValidationContext(container ?? base.Metadata.Model, null, null);
            validationContext.DisplayName = base.Metadata.GetDisplayName();
            validationContext.MemberName = base.Metadata.PropertyName;
            ValidationResult validationResult = this.Attribute.GetValidationResult(base.Metadata.Model, validationContext);
            yield break;
        }
}

 public class AntiXssDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider
 {
        protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
        {
            foreach (var attribute in attributes.OfType<AntiXssAttribute>())
            {
                yield return new AntiXssDataAnnotationsModelValidator(metadata,context,attribute);
            }
        }
}

           然后記得在Global.asax中對這個AntiXssDataAnnotationsModelValidatorProvider 進行注冊。

最后我又對AntiXssAttribute類進行了一點修改,為了在標記了該特性時不需要再額外地標記AllowHtmlAttribute:

public class AntiXssAttribute :ValidationAttribute, IMetadataAware{
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //對於XSS攻擊,只需要對string類型進行驗證就可以了
            var str = value as string;
            if (!string.IsNullOrWhiteSpace(str) && 
                validationContext.ObjectInstance != null && !
                string.IsNullOrWhiteSpace(validationContext.MemberName))
            {
                str = Sanitizer.GetSafeHtmlFragment(str);
                PropertyInfo pi = validationContext.ObjectType.GetProperty(validationContext.MemberName,
                    BindingFlags.Public | BindingFlags.Instance);
                pi.SetValue(validationContext.ObjectInstance,str);
            }
            //由於這個類的目的並不是為了驗證,所以返回驗證成功
            return ValidationResult.Success;
        }

        public void OnMetadataCreated(ModelMetadata metadata)
        {
            //實際上AllowHtmlAttribute也是實現了接口IMetadataAware,在OnMetadataCreated
            //中使用了如下的代碼
            metadata.RequestValidationEnabled = false;
        }
}

最后附上完整的代碼http://files.cnblogs.com/onepiece_wang/AntiXss.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM