MVC Anti-XSS方案
1:Form提交模式
在使用Form提交时,MVC框架提供了一个默认的机制。如果数据中含有恶意字,则会自动转向出错页面。
2:Ajax+JSON提交模式。
MVC框架未提供对于Json数据的AntiXSS支持,所以必须自行实现。
Step1:定义一个Attribute[AllowHtml],如果有这个标记,则说明该属性允许Html,不需要验证。
/// <summary> /// 用于标记某个属性是否允许html字符 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class AllowHtmlAttribute : Attribute { }
Step2:元数据解析的时候,动态设定标记为AllowHtml的属性不激发验证。
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider { public CustomModelMetadataProvider() { } protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); if (containerType == null || propertyName == null) return metadata; foreach (Attribute attr in attributes) { if (attr is AllowHtmlAttribute) { metadata.RequestValidationEnabled = false; break; } } } }
Step3:实现自定义ModerBinder,拦截所有的json数据,进行anti-xss验证。
/// <summary> /// 检测Json数据中含有恶意字符,抛出HttpRequestValidationException /// </summary> public class AntiXssModelBinder : DefaultModelBinder { protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value) { if (controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) { int index; if (controllerContext.Controller.ValidateRequest && bindingContext.PropertyMetadata[propertyDescriptor.Name].RequestValidationEnabled) { if (value is string) { if (AntiXssStringHelper.IsDangerousString(value.ToString(), out index)) { throw new HttpRequestValidationException("Dangerous Input Detected"); } } else if (value is IEnumerable) { // 字符串数组或者集合,或者Dictionary<string, string> // Dictionary的Key, Value会ToString后一起验证 foreach (object obj in value as IEnumerable) { if (obj != null) { if (AntiXssStringHelper.IsDangerousString(obj.ToString(), out index)) { throw new HttpRequestValidationException("Dangerous Input Detected"); } } } } } } return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value); } } /// <summary> /// 检测绑定的单值字符串是否包含恶意字符 /// </summary> public class AntiXssRawModelBinder : StringTrimModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var value = base.BindModel(controllerContext, bindingContext); if (value is string) { var result = (value as string).Trim(); int index; if (AntiXssStringHelper.IsDangerousString(value.ToString(), out index)) { throw new HttpRequestValidationException("Dangerous Input Detected"); } } return value; } } }
Step4:在Web站点启动的时候,配置MVC框架
RouteTableRegister.RegisterRoutes(_routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ModelMetadataProviders.Current = new CustomModelMetadataProvider(); ModelBinders.Binders.DefaultBinder = new AntiXssModelBinder();
举例:
[Required] [AllowHtml] public string UserName{get;set;} [Required] public string Password{get;set;} 注意:这时通过JSON传过来的数据Password就会报错,而UserName则不会。
如果Ajax传入的JSON是封装好的对象,最好也要经过封装,以下为例:
public ActionResult Login(LoginModel model,[ModelBinder(typeof(AntiXssRawModelBinder))]) { } //AntiXssRawModelBinder核心代码 /// <summary> /// 检测绑定的单值字符串是否包含恶意字符 /// </summary> public class AntiXssRawModelBinder : StringTrimModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var value = base.BindModel(controllerContext, bindingContext); if (value is string) { var result = (value as string).Trim(); int index; if (AntiXssStringHelper.IsDangerousString(value.ToString(), out index)) { throw new HttpRequestValidationException("Dangerous Input Detected"); } } return value; } }
如果某些参数需要支持部分HTML代码,可以采取先将该参数设置为[AllowHTML],值传到Action时,再进行手动过滤,或者 使用AntiXSS库进行编码(微软提供的AntiXSSLibrary类库)。
非常感谢您的耐心观看,您的关注是我最大的动力!
不积跬步无以至千里,不积小流无以成江海!