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

ASP.NET HTTP 模組實作 SQL 注入防護:全域攔截 QueryString/Form/Cookie 危險字元

     編輯:ASP.NET 基礎

架構定位:HTTP 模組 vs 參數化查詢

⚠️ 重要前言:現代 ASP.NET 應用首選參數化查詢 / ORM (EF/Dapper) 杜絕 SQL 注入。HTTP 模組屬於纵深防禦第二道防線,適用於:

  • 維護無法重構的舊系統
  • 第三方元件/舊程式碼可能存在拼接 SQL
  • 提供額外審計日誌與攔截記錄
  • 防禦非預期輸入向量 (Cookie、Header 等)

HTTP 模組完整實作


public class SqlInjectModule : IHttpModule
{
    private static readonly string[] _sqlKeywords;
    private static readonly string _errorPage;

    static SqlInjectModule()
    {
        // 從 web.config 讀取關鍵字與錯誤頁面
        _sqlKeywords = (ConfigurationManager.AppSettings["SqlInjectKeywords"] ?? 
            "and|exec|insert|select|delete|update|count|chr|mid|master|truncate|char|declare|drop|create|xp_|sp_|--|;|'|")
            .Split('|', StringSplitOptions.RemoveEmptyEntries);
        
        _errorPage = ConfigurationManager.AppSettings["SqlInjectErrorPage"] ?? "/Error/SqlInjectDetected.aspx";
    }

    public void Init(HttpApplication context)
    {
        // 使用 AcquireRequestState 確保 Session 已可用
        context.AcquireRequestState += OnAcquireRequestState;
    }

    private void OnAcquireRequestState(object sender, EventArgs e)
    {
        var app = (HttpApplication)sender;
        var request = app.Context.Request;
        var response = app.Context.Response;

        try
        {
            // 略過檔案上傳 (multipart/form-data)
            if (IsMultipartContent(request)) return;

            // 檢查 QueryString
            CheckCollection(request.QueryString, "QueryString", request, response);
            
            // 檢查 Form
            CheckCollection(request.Form, "Form", request, response);
            
            // 檢查 Cookies
            CheckCookies(request.Cookies, request, response);
        }
        catch (Exception ex)
        {
            // 記錄錯誤但不中斷請求 (避免模組本身導致 500)
            LogError(ex);
        }
    }

    private bool IsMultipartContent(HttpRequest request)
    {
        string ct = request.ContentType ?? "";
        return ct.StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase);
    }

    private void CheckCollection(NameValueCollection collection, string source, 
        HttpRequest request, HttpResponse response)
    {
        if (collection == null) return;
        
        foreach (string key in collection.Keys)
        {
            // 略過 ViewState
            if (key.Equals("__VIEWSTATE", StringComparison.OrdinalIgnoreCase)) continue;
            if (key.Equals("__EVENTVALIDATION", StringComparison.OrdinalIgnoreCase)) continue;
            
            string value = collection[key];
            if (ContainsSqlInjection(value))
            {
                LogAttack(source, key, value, request);
                RedirectToErrorPage(response, $"{source} 參數 '{key}' 含疑似 SQL 注入字元");
            }
        }
    }

    private void CheckCookies(HttpCookieCollection cookies, HttpRequest request, HttpResponse response)
    {
        if (cookies == null) return;
        
        foreach (string key in cookies.Keys)
        {
            var cookie = cookies[key];
            if (cookie == null) continue;
            
            if (ContainsSqlInjection(cookie.Value))
            {
                LogAttack("Cookie", key, cookie.Value, request);
                RedirectToErrorPage(response, $"Cookie '{key}' 含疑似 SQL 注入字元");
            }
        }
    }

    private bool ContainsSqlInjection(string input)
    {
        if (string.IsNullOrEmpty(input)) return false;
        
        string upperInput = input.ToUpperInvariant();
        foreach (string keyword in _sqlKeywords)
        {
            if (upperInput.Contains(keyword.ToUpperInvariant()))
                return true;
        }
        return false;
    }

    private void RedirectToErrorPage(HttpResponse response, string message)
    {
        try
        {
            response.Clear();
            response.Redirect($"{_errorPage}?msg={HttpUtility.UrlEncode(message)}");
            response.End();
        }
        catch { /* 忽略重導向錯誤 */ }
    }

    private void LogAttack(string source, string key, string value, HttpRequest request)
    {
        var log = new
        {
            Time = DateTime.Now,
            Source = source,
            Key = key,
            Value = value.Length > 200 ? value.Substring(0, 200) + "..." : value,
            IP = request.UserHostAddress,
            Url = request.RawUrl,
            UserAgent = request.UserAgent
        };
        // 寫入資料庫/檔案/ELK/Serilog
        // Logger.Warn("SQL注入攔截: {@Log}", log);
    }

    private void LogError(Exception ex)
    {
        // Logger.Error(ex, "SQL注入模組內部錯誤");
    }

    public void Dispose() { }
}

Web.config 設定

<configuration>
  <appSettings>
    <!-- 危險關鍵字,以 | 分隔,不區分大小寫 -->
    <add key="SqlInjectKeywords" value="and|exec|insert|select|delete|update|count|chr|mid|master|truncate|char|declare|drop|create|xp_|sp_|--|;|'|union|script|alert|onload|onerror|eval|expression" />
    <add key="SqlInjectErrorPage" value="/Error/SqlInjectDetected.aspx" />
  </appSettings>

  <system.webServer>
    <!-- IIS 7+ 整合模式 -->
    <modules>
      <add name="SqlInjectModule" type="YourNamespace.SqlInjectModule, YourAssembly" />
    </modules>
  </system.webServer>

  <system.web>
    <!-- IIS 6 / Classic 模式 -->
    <httpModules>
      <add name="SqlInjectModule" type="YourNamespace.SqlInjectModule, YourAssembly" />
    </httpModules>
  </system.web>
</configuration>

關鍵設計決策解析

決策點選擇理由
事件選擇AcquireRequestState比 BeginRequest 晚,Session 已載入,可記錄用戶身分
關鍵字匹配ToUpper + Contains簡單、效能可接受;避免 Regex 複雜度與 ReDoS 風險
略過 ViewState硬編碼排除ViewState 含 Base64 經常觸發誤判
略過 multipartContentType 判斷檔案二進位內容會大量誤判,且通常不經 SQL
錯誤處理Try-Catch 包裹全流程模組錯誤不應導致整站掛掉

誤判風險與緩解策略

關鍵字黑名單必然產生誤判 (如搜尋 "Android" 含 "and"、部落格內容含 "select")。緩解方案:

  1. 白名單路徑排除:搜尋頁、編輯器頁、API 端點排除攔截
  2. 語境感知檢測:結合 SQL 解析器 (如 `Microsoft.Data.SqlClient.SqlParser`) 分析語法結構
  3. 記錄不攔截模式:僅記錄可疑請供安全團隊分析,不阻擋用戶
  4. 關鍵字精簡:移除極高誤判字元 (如單獨 `'`、`;`) ,保留高風險組合 (`union select`, `exec xp_`)

現代化替代方案

方案優勢適用場景
WAF (Web Application Firewall)層級更高、規則庫更新、不侵入程式碼生產環境必備 (Azure WAF, AWS WAF, ModSecurity, Cloudflare)
參數化查詢 + 程式碼審查根治源頭、零誤判、效能最佳所有新開發專案
ORM (EF Core / Dapper)強制參數化、編譯時檢查現代 .NET 專案標配
RASP (Runtime Application Self-Protection)運行時監控、零規則維護高安全需求企業級應用

總結

HTTP 模組 SQL 注入防護是維護舊系統的權宜之計,非長久之道。核心實作要點:AcquireRequestState 事件、排除 ViewState/multipart、關鍵字大小寫不敏感匹配、完整審計日誌、錯誤頁面友善導向。新專案請直接採用參數化查詢 + ORM + WAF 三層防護體系,將安全風險消除於開發階段。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved