public class GlobalXssValidationHandler : DelegatingHandler
{
    /// <summary>
    /// 重写 SendAsync 方法,拦截所有 HTTP 请求
    /// </summary>
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        bool XssValidationEnabled = Convert.ToBoolean(Config.XssValidationEnabled);

        if (XssValidationEnabled)
        {
            // 步骤1:捕获并读取请求体,转换为字符串(保留原始流,不影响后续处理)
            string requestBody = string.Empty;
            // 新增判断:如果是文件上传请求(multipart/form-data),则不执行XSS校验
            bool isFileUpload = request.Content != null && request.Content.Headers.ContentType != null
                && request.Content.Headers.ContentType.MediaType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase);

            if (!isFileUpload && request.Content != null && request.Content.Headers.ContentLength > 0)
            {
                var contentStream = await request.Content.ReadAsStreamAsync();
                contentStream.Position = 0; // 重置流指针到起始位置
                using (var streamReader = new StreamReader(contentStream, Encoding.UTF8, true, 1024, true))
                {
                    requestBody = await streamReader.ReadToEndAsync();
                }
                contentStream.Position = 0; // 重置流指针,供后续 Controller 读取

                // 步骤2:直接调用你已有的 XSS 校验方法(核心集成点)
                // 假设你的校验方法名为:MyExistingXssCheck,返回 bool(true表示安全,false表示可能包含XSS攻击)
                bool hasXss = SafeXssValidator.IsSafeInput(requestBody);

                // 步骤3:校验失败,直接返回 400 错误,阻止请求继续传递
                if (!hasXss)
                {
                    return request.CreateErrorResponse(
                        HttpStatusCode.BadRequest,
                        AutoCEP.XSSErrorMsg);
                }
            }
        }

        // 校验通过,继续传递请求到后续管道(Controller、Action 等)
        var response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}
/// <summary>
/// 允许特殊字符输入的XSS防护验证器
/// 核心策略:不拦截特殊字符,而是通过输出编码确保安全,同时拦截明显的脚本攻击
/// </summary>
public static class SafeXssValidator
{
    // 定义危险标签列表
    private static readonly HashSet<string> DangerousTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "script", "iframe", "svg", "a", "img","input","form","object"
    };

    // 定义危险函数列表
    private static readonly HashSet<string> DangerousFunctions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "eval", "function", "document.write"
    };

    // 定义危险关键词列表(用于事件处理程序内容)
    private static readonly HashSet<string> DangerousKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "tostring", "atob", "eval", "alert", "prompt", "confirm", "top",
        "document.cookie", "window.local", "document.local", "sessionstorage"
    };

    // 特殊字符模式(用于检测混淆的攻击载荷)
    private static readonly string SpecialCharPattern = @"[-~\[\]\+\(\)\!]+";

    /// <summary>
    /// 检查输入是否包含潜在的XSS攻击载荷
    /// </summary>
    /// <param name="input">用户输入字符串</param>
    /// <returns>true表示安全,false表示可能包含XSS攻击</returns>
    public static bool IsSafeInput(string input)
    {
        if (string.IsNullOrEmpty(input))
            return true;

        // 检查危险标签
        if (ContainsDangerousTags(input))
            return false;

        // 检查危险事件属性
        if (ContainsDangerousEvents(input))
            return false;

        // 检查危险函数调用
        if (ContainsDangerousFunctions(input))
            return false;

        // 检查href/src属性中的危险链接
        if (ContainsDangerousUrls(input))
            return false;

        return true;
    }

    /// <summary>
    /// 检查是否包含危险标签
    /// </summary>
    private static bool ContainsDangerousTags(string input)
    {
        // 使用正则表达式匹配HTML标签
        var tagPattern = @"<(\w+)[^>]*>";
        var matches = Regex.Matches(input, tagPattern, RegexOptions.IgnoreCase);

        foreach (Match match in matches)
        {
            if (match.Groups.Count > 1)
            {
                string tagName = match.Groups[1].Value.ToLower();
                if (DangerousTags.Contains(tagName))
                {
                    return true;
                }
            }
        }

        return false;
    }

    /// <summary>
    /// 检查是否包含危险事件属性
    /// </summary>
    private static bool ContainsDangerousEvents(string input)
    {
        // 检查尖括号内是否包含任何on事件
        var onEventInTagPattern = @"<[^>]*\bon\w+\s*=[^>]*>";
        if (Regex.IsMatch(input, onEventInTagPattern, RegexOptions.IgnoreCase))
        {
            return true;
        }

        // 检查是否包含特殊字符模式
        if (Regex.IsMatch(input, SpecialCharPattern))
        {
            // 进一步检查特殊字符模式是否与已知攻击模式匹配
            if (ContainsSuspiciousPattern(input))
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 检查事件处理程序内容是否包含危险关键词
    /// </summary>
    private static bool ContainsDangerousKeywordsInEventHandler(string input, int startIndex)
    {
        // 查找事件处理程序的内容(直到下一个属性或标签结束)
        int endIndex = input.IndexOfAny(new[] { ' ', '>', '\'', '"' }, startIndex);
        if (endIndex == -1) endIndex = input.Length;

        if (startIndex >= endIndex) return false;

        string handlerContent = input.Substring(startIndex, endIndex - startIndex).Trim();

        // 移除可能的引号
        handlerContent = handlerContent.Trim('\'', '"', ' ');

        // 检查是否包含危险关键词
        foreach (var keyword in DangerousKeywords)
        {
            if (handlerContent.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                return true;
            }
        }

        // 检查是否包含特殊字符模式(混淆的攻击载荷)
        if (Regex.IsMatch(handlerContent, SpecialCharPattern))
        {
            // 进一步检查特殊字符模式是否与已知攻击模式匹配
            if (ContainsSuspiciousPattern(handlerContent))
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 检查是否包含可疑的攻击模式
    /// </summary>
    private static bool ContainsSuspiciousPattern(string content)
    {
        // 这里可以添加更复杂的模式检测逻辑
        // 例如检测常见的JS混淆技术

        // 简单的检测:如果包含大量特殊字符且长度较短,可能是混淆的攻击载荷
        int specialCharCount = Regex.Matches(content, SpecialCharPattern).Count;
        if (specialCharCount > 3 && content.Length < 50)
        {
            return true;
        }

        return false;
    }

    /// <summary>
    /// 检查是否包含危险函数调用
    /// </summary>
    private static bool ContainsDangerousFunctions(string input)
    {
        foreach (var function in DangerousFunctions)
        {
            // 简单的函数名检测
            if (input.IndexOf(function, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                // 进一步检查是否是函数调用模式
                var functionPattern = $@"{function}\s*\(";
                if (Regex.IsMatch(input, functionPattern, RegexOptions.IgnoreCase))
                {
                    return true;
                }
            }
        }

        return false;
    }

    /// <summary>
    /// 检查href/src属性中的危险链接
    /// </summary>
    private static bool ContainsDangerousUrls(string input)
    {
        // 匹配href和src属性
        var urlPattern = @"(href|src)\s*=\s*([""']?)([^""'\s>]+)\2";
        var matches = Regex.Matches(input, urlPattern, RegexOptions.IgnoreCase);

        foreach (Match match in matches)
        {
            if (match.Groups.Count > 3)
            {
                string url = match.Groups[3].Value;

                // 检查是否是合法的URL格式
                if (!IsValidUrl(url))
                {
                    return true;
                }

                // 检查是否是javascript:或data:等危险协议
                if (url.Trim().StartsWith("javascript:", StringComparison.OrdinalIgnoreCase) ||
                    url.Trim().StartsWith("data:", StringComparison.OrdinalIgnoreCase) ||
                    url.Trim().StartsWith("vbscript:", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
        }

        return false;
    }

    /// <summary>
    /// 检查URL是否合法
    /// </summary>
    private static bool IsValidUrl(string url)
    {
        // 先修剪字符串并转换为小写以便检查
        string trimmedUrl = url.Trim().ToLowerInvariant();

        // 检查危险协议(使用更健壮的方式)
        if (trimmedUrl.StartsWith("javascript:") ||
            trimmedUrl.StartsWith("data:") ||
            trimmedUrl.StartsWith("vbscript:") ||
            trimmedUrl.StartsWith("jscript:") ||
            trimmedUrl.StartsWith("livescript:") ||
            trimmedUrl.StartsWith("about:"))
        {
            return false; // 是不安全的协议
        }

        // 合法的URL应该以http://或https://开头
        if (trimmedUrl.StartsWith("http://") ||
            trimmedUrl.StartsWith("https://"))
        {
            return true;
        }

        // 相对URL也是可以接受的
        if (trimmedUrl.StartsWith("/") ||
            trimmedUrl.StartsWith("./") ||
            trimmedUrl.StartsWith("../"))
        {
            return true;
        }

        // 锚点链接
        if (trimmedUrl.StartsWith("#"))
        {
            return true;
        }

        // 邮件链接
        if (trimmedUrl.StartsWith("mailto:"))
        {
            return true;
        }

        // 其他情况视为不安全
        return false;
    }

    /// <summary>
    /// 安全编码输出字符串(额外的防护层)
    /// </summary>
    public static string SafeEncode(string input)
    {
        if (string.IsNullOrEmpty(input))
            return string.Empty;

        return System.Web.HttpUtility.HtmlEncode(input);
    }
}

更多推荐