跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的特殊目的。
很多时候,我们并不需要屏蔽所有的HTML标签,或者,我们需要设置某些属性支持的HTML标签字符串。还好,框架中封装了相关的特性,以便你直接拿来使用。
命名空间:Magicodes.WeiChat.Infrastructure.MvcExtension.Filters
类名:AntiXssAttribute
Demo:
[AntiXss] public string Name { get; set; } [AntiXss(allowedStrings: "<br />,<p>")] public string Description { get; set; } [AntiXss(allowedStrings: "<br />", disallowedStrings:"/, #")] public string NoSlashesOrHashes { get; set; } [AntiXss(errorMessage: "This is a custom error message")] public string CustomError { get; set; } [AntiXss(errorMessageResourceName:"TestMessage", errorMessageResourceType: typeof(TestResources))] public string ResourceCustomError { get; set; }
具体代码如下所示:
/// <summary> /// AntiXss验证特性,防止XSS攻击 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class AntiXssAttribute : ValidationAttribute { const string DefaultValidationMessageFormat = "字段 {0} XSS验证失败,请检查输入的字符串中是否含有非法字符。"; private readonly string errorMessage; private readonly string errorMessageResourceName; private readonly Type errorMessageResourceType; private readonly string allowedStrings; private readonly string disallowedStrings; private readonly Dictionary<string, string> allowedStringsDictionary; /// <summary> /// 初始化 <see cref="AntiXssAttribute"/> 的新实例. /// </summary> /// <param name="errorMessage">错误消息</param> /// <param name="errorMessageResourceName">获取或设置错误消息资源的名称,在验证失败的情况下,要使用该名称来查找 ErrorMessageResourceType 属性值</param> /// <param name="errorMessageResourceType">获取或设置在验证失败的情况下用于查找错误消息的资源类型。</param> /// <param name="allowedStrings">以逗号分隔的允许的字符串。</param> /// <param name="disallowedStrings">以逗号分隔的字符串不允许的字符或单词</param> public AntiXssAttribute( string errorMessage = null, string errorMessageResourceName = null, Type errorMessageResourceType = null, string allowedStrings = null, string disallowedStrings = null) { this.errorMessage = errorMessage; this.errorMessageResourceName = errorMessageResourceName; this.errorMessageResourceType = errorMessageResourceType; this.allowedStrings = allowedStrings; this.disallowedStrings = disallowedStrings; allowedStringsDictionary = new Dictionary<string, string>(); } /// <summary> /// 确定对象的指定值是否有效。 /// </summary> /// <param name="value"></param> /// <returns></returns> public override bool IsValid(object value) { return true; } /// <summary> /// 确定对象的指定值是否有效。 /// </summary> /// <param name="value"></param> /// <param name="validationContext"></param> /// <returns></returns> protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null) { return base.IsValid(null, validationContext); } var encodedValue = EncoderHelper.HtmlEncode(value.ToString(), false); if (EncodedStringAndValueAreDifferent(value, encodedValue)) { SetupAllowedStringsDictionary(); foreach (var allowedString in allowedStringsDictionary) { encodedValue = encodedValue.Replace(allowedString.Value, allowedString.Key); } if (EncodedStringAndValueAreDifferent(value, encodedValue)) { return new ValidationResult(SetErrorMessage(validationContext)); } } if (!string.IsNullOrWhiteSpace(disallowedStrings) && disallowedStrings.Split(',').Select(x => x.Trim()).Any(x => value.ToString().Contains(x))) { return new ValidationResult(SetErrorMessage(validationContext)); } return base.IsValid(value, validationContext); } private static bool EncodedStringAndValueAreDifferent(object value, string encodedValue) { return !value.ToString().Equals(encodedValue); } private void SetupAllowedStringsDictionary() { if (string.IsNullOrWhiteSpace(allowedStrings)) { return; } foreach (var allowedString in allowedStrings.Split(',').Select(x => x.Trim()) .Where(allowedString => !allowedStringsDictionary.ContainsKey(allowedString))) { allowedStringsDictionary.Add(allowedString, EncoderHelper.HtmlEncode(allowedString, false)); } } /// <summary> /// 设置错误消息 /// </summary> /// <param name="validationContext"></param> /// <returns></returns> private string SetErrorMessage(ValidationContext validationContext) { if (IsResourceErrorMessage()) { var resourceManager = new ResourceManager(errorMessageResourceType); return resourceManager.GetString(errorMessageResourceName, CultureInfo.CurrentCulture); } if (!string.IsNullOrEmpty(errorMessage)) { return errorMessage; } return string.Format(DefaultValidationMessageFormat, validationContext.DisplayName); } private bool IsResourceErrorMessage() { return !string.IsNullOrEmpty(errorMessageResourceName) && errorMessageResourceType != null; } }