欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Asp.net Mvc中利用ValidationAttribute实现xss过滤

程序员文章站 2022-03-26 19:24:18
在网站开发中,需要注意的一个问题就是防范xss攻击,asp.net m中已经自动为我们提供了这个功能。用户提交数据时时,在生成action参数的过程中.net会对用户提交的数据进行验证,一旦发现提交...
在网站开发中,需要注意的一个问题就是防范xss攻击,asp.net m中已经自动为我们提供了这个功能。用户提交数据时时,在生成action参数的过程中.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;

        }

}