首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 全站防注入攻擊:Global.asax 統一參數驗證攔截器實作

     編輯:ASP.NET 基礎

SQL 注入、XSS、指令注入是 Web 應用最常見的安全威脅。在 ASP.NET Web Forms 中,透過 `Global.asax` 的 `Application_BeginRequest` 事件攔截**所有進入請求**,統一驗證 QueryString、Form、Cookie 參數,配合 `Web.config` 設定驗證規則,可建立「零侵入、全站覆蓋、易維護」的防護層。本文提供完整實作方案。

核心架構

  1. Web.config:定義參數名稱、預期型別、驗證規則 (白名單模式)
  2. ParameterValidator:靜態驗證類別,含 Regex、型別轉換、長度限制等邏輯
  3. Global.asax:`Application_BeginRequest` 遍歷設定、驗證參數、違規導向錯誤頁
  4. Error Page:友善錯誤提示,記錄攻擊日誌 (IP、參數、時間、User-Agent)

1. Web.config 設定區段

<configuration>
  <configSections>
    <section name="parameterValidation" type="System.Configuration.NameValueSectionHandler" />
  </configSections>

  <parameterValidation>
    <!-- 格式: 參數名稱 = 型別[:正則/最大長度/範圍] -->
    <add key="id" value="int32" />
    <add key="page" value="int32:1-1000" />
    <add key="email" value="email" />
    <add key="username" value="string:1-50:^[a-zA-Z0-9_]+$" />
    <add key="search" value="string:0-100" />
    <add key="orderId" value="guid" />
    <add key="amount" value="decimal:0-999999.99" />
    <add key="date" value="date:yyyy-MM-dd" />
  </parameterValidation>
</configuration>

2. 參數驗證核心類別

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Web;

public static class ParameterValidator
{
    private static readonly NameValueCollection _rules = 
        (NameValueCollection)ConfigurationManager.GetSection("parameterValidation");

    /// 驗證單一參數值
    public static bool Validate(string paramName, string paramValue, out string errorMessage)
    {
        errorMessage = null;
        if (string.IsNullOrEmpty(paramValue)) return true; // 空值視為通過 (必要性由業務邏輯決定)

        if (_rules == null || _rules[paramName] == null) 
            return true; // 未設定規則的參數預設放行 (或可改為封鎖)

        string rule = _rules[paramName];
        string[] parts = rule.Split(':');
        string type = parts[0].ToLower();

        try
        {
            switch (type)
            {
                case "int32":
                    if (!int.TryParse(paramValue, out int intVal)) return Fail("非整數");
                    if (parts.Length > 1 && !CheckRange(intVal, parts[1])) return Fail("超出範圍");
                    break;

                case "int64":
                    if (!long.TryParse(paramValue, out long longVal)) return Fail("非長整數");
                    if (parts.Length > 1 && !CheckRange(longVal, parts[1])) return Fail("超出範圍");
                    break;

                case "decimal":
                    if (!decimal.TryParse(paramValue, out decimal decVal)) return Fail("非數值");
                    if (parts.Length > 1 && !CheckRange(decVal, parts[1])) return Fail("超出範圍");
                    break;

                case "guid":
                    if (!Guid.TryParse(paramValue, out _)) return Fail("非合法 GUID");
                    break;

                case "date":
                    string format = parts.Length > 1 ? parts[1] : "yyyy-MM-dd";
                    if (!DateTime.TryParseExact(paramValue, format, null, System.Globalization.DateTimeStyles.None, out _))
                        return Fail($"日期格式錯誤,應為 {format}");
                    break;

                case "email":
                    if (!Regex.IsMatch(paramValue, @"^[\w\.-]+@[\w\.-]+\.\w{2,}$")) return Fail("Email 格式錯誤");
                    break;

                case "string":
                    int maxLen = 255;
                    string pattern = null;
                    if (parts.Length > 1)
                    {
                        // 格式: maxLen 或 maxLen:regex
                        string[] lenParts = parts[1].Split(':');
                        if (int.TryParse(lenParts[0], out int ml)) maxLen = ml;
                        if (lenParts.Length > 1) pattern = lenParts[1];
                    }
                    if (paramValue.Length > maxLen) return Fail($"長度超過 {maxLen}");
                    if (!string.IsNullOrEmpty(pattern) && !Regex.IsMatch(paramValue, pattern))
                        return Fail("格式不符合規則");
                    break;

                default:
                    return true; // 未知型別放行
            }
            return true;
        }
        catch (Exception ex)
        {
            errorMessage = ex.Message;
            return false;
        }

        bool Fail(string msg)
        {
            errorMessage = $"參數 [{paramName}] {msg}: {paramValue}";
            return false;
        }
    }

    private static bool CheckRange(T value, string rangeSpec) where T : IComparable
    {
        // 格式: min-max
        string[] bounds = rangeSpec.Split('-');
        if (bounds.Length != 2) return true;
        if (T.TryParse(bounds[0], out T min) && value.CompareTo(min) < 0) return false;
        if (T.TryParse(bounds[1], out T max) && value.CompareTo(max) > 0) return false;
        return true;
    }

    /// 驗證 HttpRequest 所有輸入集合
    public static bool ValidateRequest(HttpRequest request, out string errorMessage)
    {
        errorMessage = null;
        // 合併 QueryString、Form、Cookies (可擴充 Headers)
        var collections = new NameValueCollection[] { request.QueryString, request.Form, request.Cookies };
        
        foreach (var coll in collections)
        {
            foreach (string key in coll)
            {
                if (string.IsNullOrEmpty(key)) continue;
                string value = coll[key];
                if (!Validate(key, value, out errorMessage))
                    return false;
            }
        }
        return true;
    }
}

3. Global.asax 攔截實作

public class Global : System.Web.HttpApplication
{
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        // 可選:排除靜態資源、特定路徑
        string path = Request.Path.ToLower();
        if (path.EndsWith(".css") || path.EndsWith(".js") || path.EndsWith(".png") || 
            path.EndsWith(".jpg") || path.EndsWith(".ico") || path.Contains("/api/public/"))
            return;

        if (!ParameterValidator.ValidateRequest(Request, out string errorMsg))
        {
            // 記錄攻擊日誌
            LogAttack(Request, errorMsg);

            // 導向錯誤頁 (避免回傳 500 洩露資訊)
            Response.Clear();
            Response.StatusCode = 400;
            Server.Transfer("~/Security/ParameterError.aspx?msg=" + HttpUtility.UrlEncode(errorMsg));
            Response.End();
        }
    }

    private void LogAttack(HttpRequest request, string errorMsg)
    {
        var log = new
        {
            Time = DateTime.Now,
            IP = request.UserHostAddress,
            Url = request.RawUrl,
            Method = request.HttpMethod,
            UserAgent = request.UserAgent,
            Referrer = request.UrlReferrer?.ToString(),
            Error = errorMsg,
            Params = GetSafeParams(request) // 避免記錄敏感欄位 (密碼、Token)
        };
        // 寫入資料庫、檔案、ELK、Application Insights 等
        System.Diagnostics.Trace.WriteLine($"[SECURITY] {Newtonsoft.Json.JsonConvert.SerializeObject(log)}");
    }

    private string GetSafeParams(HttpRequest request)
    {
        var safe = new NameValueCollection();
        string[] sensitive = { "password", "pwd", "token", "secret", "key", "auth", "credential" };
        foreach (string key in request.Params)
        {
            if (!Array.Exists(sensitive, s => key.ToLower().Contains(s)))
                safe[key] = request.Params[key];
        }
        return Newtonsoft.Json.JsonConvert.SerializeObject(safe);
    }
}

4. 錯誤頁面

<%@ Page Language="C#" %>
<!DOCTYPE html>
<html><head><title>參數驗證失敗</title></head><body>
    <h2>請求參數不合法</h2>
    <p>系統偵測到異常參數,請求已被攔截。</p>
    <p><strong>詳細資訊:</strong> <%= Server.HtmlEncode(Request.QueryString["msg"]) %></p>
    <p><a href="javascript:history.back()">返回上一頁</a></p>
</body></html>

進階擴充:白名單 vs 黑名單、WAF 整合

策略優點缺點適用場景
白名單 (本方案)安全性高、精確控制、誤報低新增參數需修改設定、初期維護成本高企業級應用、金融/醫療/政府系統
黑名單 (關鍵字過濾)部署快、無需改代碼繞過技巧多、誤攔正常輸入 (如 "select * from user")緊急臨時防護、舊系統無法重構時
WAF (Web Application Firewall)零代碼變更、規則庫自動更新、防禦 0-day成本高、可能誤攔、需調校生產環境前置防護 (Azure WAF, AWS WAF, Cloudflare, ModSecurity)

現代化替代:ASP.NET Core Middleware

// Program.cs
app.Use(async (context, next) =>
{
    // 同樣邏輯驗證 Query、Form、Body
    if (!ValidateParameters(context.Request))
    {
        context.Response.StatusCode = 400;
        await context.Response.WriteAsync("Invalid parameters");
        return;
    }
    await next();
});

// 或使用 FluentValidation + 自動驗證管線
builder.Services.AddControllers(options => 
    options.Filters.Add());

部署檢核清單

  • ☐ 所有公開參數皆在 `parameterValidation` 設定規則
  • ☐ 敏感參數 (密碼、Token) 排除日誌記錄
  • ☐ 錯誤頁面不洩露堆疊追蹤、資料庫結構、伺服器資訊
  • ☐ 記錄攻擊日誌並設定告警 (連續失敗 > N 次 / 分鐘)
  • ☐ 定期審查規則:新增功能同步新增驗證規則
  • ☐ 生產環境前置部署 WAF (雲端原生或 ModSecurity)
結論: `Global.asax` 統一參數驗證是 ASP.NET Web Forms 時代最實用的「零侵入」防注入方案。核心在於**白名單型別驗證 + 設定外部化 + 完整稽核日誌**。現代專案請遷移至 ASP.NET Core Middleware + FluentValidation + WAF 多層防護體系。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved