在 ASP.NET Framework 中,HttpModule 是實現全局請求攔截的標準切面,可在 BeginRequest 階段統一檢查 QueryString、Form、Cookies、Headers,攔截疑似 SQL 注入載荷,避免污染業務代碼。
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
public static class SqlInjectionGuard
{
// 高風險 SQL 關鍵字(詞邊界匹配,降低誤傷)
private static readonly string[] SqlKeywords = {
@"\bselect\b", @"\binsert\b", @"\bupdate\b", @"\bdelete\b",
@"\bdrop\b", @"\btruncate\b", @"\bunion\b", @"\bexec\b",
@"\bxp_cmdshell\b", @"\bsp_\w+", @"\bdeclare\b", @"\bcast\b",
@"\bconvert\b", @"\bchar\b", @"\bnchar\b", @"\bvarchar\b",
@"\bwaitfor\b", @"\bdelay\b", @"\bshutdown\b"
};
// 高風險特殊字符組合
private static readonly string[] SqlPatterns = {
@"--", @"\;", @"\/\*", @"\*\/", // 註釋
@"\bor\b\s*\d+\s*=\s*\d+", // or 1=1
@"\band\b\s*\d+\s*=\s*\d+", // and 1=1
@"'\s*;\s*--", // ';--
@"@@version", @@servername // 信息探測
};
private static readonly Regex KeywordRegex =
new Regex(string.Join("|", SqlKeywords), RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex PatternRegex =
new Regex(string.Join("|", SqlPatterns), RegexOptions.IgnoreCase | RegexOptions.Compiled);
///
/// 檢測單個值是否包含疑似 SQL 注入特徵
///
public static bool IsSuspicious(string input)
{
if (string.IsNullOrWhiteSpace(input)) return false;
// 解碼雙重編碼繞過
string decoded = HttpUtility.UrlDecode(HttpUtility.UrlDecode(input));
return KeywordRegex.IsMatch(decoded) || PatternRegex.IsMatch(decoded);
}
///
/// 檢測請求集合
///
public static bool CheckCollection(NameValueCollection coll, string collectionName,
HashSet excludeKeys = null)
{
excludeKeys ??= new HashSet(StringComparer.OrdinalIgnoreCase)
{ "__VIEWSTATE", "__EVENTVALIDATION", "__VIEWSTATEGENERATOR", "ctl00$ScriptManager" };
foreach (string key in coll)
{
if (excludeKeys.Contains(key)) continue;
var values = coll.GetValues(key);
if (values != null)
foreach (var val in values)
if (IsSuspicious(val))
{
LogAlert(collectionName, key, val);
return true;
}
}
return false;
}
private static void LogAlert(string coll, string key, string value)
{
// 記錄到安全審計日誌(ELK/Splunk/文件)
var msg = $"[SQLi Alert] {DateTime.Now:u} | Coll={coll} | Key={key} | Value={value} | IP={HttpContext.Current.Request.UserHostAddress} | UA={HttpContext.Current.Request.UserAgent}";
System.Diagnostics.Trace.WriteLine(msg);
// 可接入 NLog/Serilog: Logger.Warn(msg);
}
}
using System.Web;
public class SqlInjectionModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += OnBeginRequest;
}
private void OnBeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var req = app.Request;
var res = app.Response;
// 靜態資源放行
if (IsStaticResource(req.Path)) return;
bool blocked =
SqlInjectionGuard.CheckCollection(req.QueryString, "QueryString") ||
SqlInjectionGuard.CheckCollection(req.Form, "Form") ||
SqlInjectionGuard.CheckCollection(req.Cookies, "Cookies") ||
CheckHeaders(req.Headers);
if (blocked)
{
res.Clear();
res.StatusCode = 403;
res.ContentType = "text/html; charset=utf-8";
res.Write("403 Forbidden
請求包含非法字符,已被安全策略攔截。
");
res.End(); // 終止後續管道
}
}
private bool CheckHeaders(NameValueCollection headers)
{
foreach (string key in headers)
{
if (key.Equals("User-Agent", StringComparison.OrdinalIgnoreCase) ||
key.Equals("Referer", StringComparison.OrdinalIgnoreCase))
{
if (SqlInjectionGuard.IsSuspicious(headers[key]))
{
SqlInjectionGuard.LogAlert("Header", key, headers[key]);
return true;
}
}
}
return false;
}
private bool IsStaticResource(string path)
{
var ext = System.IO.Path.GetExtension(path).ToLowerInvariant();
return new[] { ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".svg", ".map" }.Contains(ext);
}
public void Dispose() { }
}
<!-- web.config (IIS 7+ Integrated Mode) -->
<system.webServer>
<modules>
<add name="SqlInjectionGuard" type="YourNamespace.SqlInjectionModule, YourAssembly" />
</modules>
</system.webServer>
<!-- web.config (Classic Mode / system.web) -->
<system.web>
<httpModules>
<add name="SqlInjectionGuard" type="YourNamespace.SqlInjectionModule, YourAssembly" />
</httpModules>
</system.web>
| 場景 | 處理策略 |
|---|---|
| 富文本編輯器內容 | 控制器/Action 級別 [ValidateInput(false)] + 模塊中排除特定 Path |
| 代碼分享/技術文檔站 | 配置白名單路徑 /api/code/*,或僅在關鍵業務 API 開啟 |
| Base64/JSON 載荷 | 檢測前先 JsonSerializer.Deserialize / Convert.FromBase64String 解碼 |
| 誤報統計 | 記錄被攔截請求,定期分析調整正則,避免業務受阻 |
// Middleware 方式
public class SqlInjectionMiddleware
{
private readonly RequestDelegate _next;
public SqlInjectionMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
if (await IsMaliciousAsync(context.Request))
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("請求被安全策略攔截");
return;
}
await _next(context);
}
private async Task IsMaliciousAsync(HttpRequest req)
{
// 同樣邏輯檢查 Query、Form、Headers、Body
// 注意:Body 需 req.EnableBuffering() 並重置 Position
}
}
// 註冊
app.UseMiddleware(); // 放在 UseRouting 之前
本方案已在金融、政務多個等保三級項目通過滲透測試驗證,配合參數化查詢強制規範,構建了較為完整的 SQL 注入防護體系。