在网站开发中,需要注意的一个问题就是防范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

Asp.net Mvc中利用ValidationAttribute实现xss过滤的更多相关文章

  1. ASP.NET MVC中利用AuthorizeAttribute实现访问身份是否合法以及Cookie过期问题的处理

    话说来到上海已经快半年了,时光如白驹过隙,稍微不注意,时间就溜走了,倒是没有那么忙碌,闲暇之际来博客园还是比较多的,记得上次在逛博问的时候看到有同志在问MVC中Cookie过期后如何作相关处理,他在阐 ...

  2. asp.net mvc 5 利用ActionFilterAttribute实现权限过滤

    关于c#属性的教程:http://www.runoob.com/csharp/csharp-attribute.html 在asp.net mvc5中,可以利用ActionFilterAttribut ...

  3. MVC中利用自定义的ModelBinder过滤关键字

    上一篇主要讲解了如何利用ActionFilter过滤关键字,这篇主要讲解如何利用自己打造的ModelBinder来过滤关键字. 首先,我们还是利用上一篇中的实体类,但是我们需要加上DataType特性 ...

  4. 在ASP.NET MVC中利用Aspose.cells 将查询出的数据导出为excel,并在浏览器中下载。

    正题前的唠叨 本人是才出来工作不久的小白菜一颗,技术很一般,总是会有遇到一些很简单的问题却不知道怎么做,这些问题可能是之前解决过的.发现这个问题,想着提升一下自己的技术水平,将一些学的新的'好'东西记 ...

  5. 在Asp.Net MVC中利用快递100接口实现订阅物流轨迹功能

    前言 分享一篇关于在电商系统中同步物流轨迹到本地服务器的文章,当前方案使用了快递100做为数据来源接口,这个接口是收费的,不过提供的功能还是非常强大的,有专门的售后维护团队.也有免费的方案,类似于快递 ...

  6. 在 ASP.NET MVC 中充分利用 WebGrid (microsoft 官方示例)

    在 ASP.NET MVC 中充分利用 WebGrid https://msdn.microsoft.com/zh-cn/magazine/hh288075.aspx Stuart Leeks 下载代 ...

  7. 在Asp.Net MVC中实现RequiredIf标签对Model中的属性进行验证

    在Asp.Net MVC中可以用继承ValidationAttribute的方式,自定制实现RequiredIf标签对Model中的属性进行验证 具体场景为:某一属性是否允许为null的验证,要根据另 ...

  8. 在Asp.Net MVC中实现CompareValues标签对Model中的属性进行验证

    在Asp.Net MVC中可以用继承ValidationAttribute的方式,自定制实现Model两个中两个属性值的比较验证 具体应用场景为:要对两个属性值的大小进行验证 代码如下所示: /// ...

  9. ASP.NET MVC中的拦截器

    在ASP.NET MVC中,有三种拦截器:Action拦截器.Result拦截器和Exception拦截器, 所谓的拦截器也没有什么的,只是写一个类,继承另一个类和一个接口,顺便实现接口里面的方法而以 ...

随机推荐

  1. 后台js弹提示

    StringBuffer sb=new StringBuffer(); try{ sb.append("<script> location.href=\"member_ ...

  2. SaltStack配置管理之状态模块和jinja2(五)

    官方文档 https://docs.saltstack.com/en/latest/topics/states/index.html 配置管理之SLS Salt  State  SLS描述文件(YAM ...

  3. mysql数据库delete数据时不支持表别名

    今天在帮同事查看一条删除的SQL语句执行出错的问题 SQL语句如下: 1 DELETE FROM LEAD_SYSTEM_MENU_ORG_REF as t WHERE t.resourceid='4 ...

  4. 浅谈C中的指针和数组(五)

    前面写了一些C指针和数组的一些知识,但是还有一些很重要的知识没有交代,这里做一个补充. 首先看一下,普通变量(指针也是变量)和数组名查看地址的方式是不同的. 查看数组变量的地址,不需要使用 & ...

  5. 其它综合-使用Putty远程连接管理Linux实践

    使用Putty远程连接管理Linux实践 1.获取putty 获取 putty有很多方法,以下是我为大家提供的下载地址: 个人网盘地址,提取码:tz83 官方下载地址 解释: 官方下载的是 zip 压 ...

  6. 网站建设部署与发布--笔记2-部署Apache

    网站部署(Linux) 部署Apache 操作系统:CentOS 7.2 1.首先连接云服务器,清楚系统垃圾. $ yum clean all Loaded plugins: fastestmirro ...

  7. SpringBoot入门笔记(二)、使用fastjson

    1.添加fastjson配置 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastj ...

  8. java.lang.OutOfMemoryError: unable to create new native thread 居然是MQ问题

    问题: 开发环境,之前一直正常,某天突然用tomcat启动项目后时不时报如下错误: java.lang.OutOfMemoryError: unable to create new native th ...

  9. 关键字super和this的使用及区别

    "this"作为一个特殊的关键字,它的规则如下: 1.可以表示构造函数传递.this(a,b)表示调用另外一个构造函数.这里面的this就是一个特殊语法,不是变量,没有什么类型. ...

  10. POJ 1035 Spell checker 字符串 难度:0

    题目 http://poj.org/problem?id=1035 题意 字典匹配,单词表共有1e4个单词,单词长度小于15,需要对最多50个单词进行匹配.在匹配时,如果直接匹配可以找到待匹配串,则直 ...