開發工具:Visual Studio 2017
C#版本:C#7.1
最有效的防止SQL注入的方式是調用數據庫時使用參數化查詢。
但是如果是接手一個舊的WebApi項目,不想改繁多的數據庫訪問層的代碼,應該如何做。
我的解決方案是加一個過濾器。
先寫過濾方法,上代碼
using System; using System.Collections.Generic; using System.Web; namespace Test { /// <summary> /// 防止SQL注入 /// </summary> public class AntiSqlInject { public static AntiSqlInject Instance = new AntiSqlInject(); /// <summary> /// 初始化過濾方法 /// </summary> static AntiSqlInject() { SqlKeywordsArray.AddRange(SqlSeparatKeywords.Split('|')); SqlKeywordsArray.AddRange(Array.ConvertAll(SqlCommandKeywords.Split('|'), h => h + " ")); SqlKeywordsArray.AddRange(Array.ConvertAll(SqlCommandKeywords.Split('|'), h => " " + h)); } private const string SqlCommandKeywords = "and|exec|execute|insert|select|delete|update|count|chr|mid|master|" + "char|declare|sitename|net user|xp_cmdshell|or|create|drop|table|from|grant|use|group_concat|column_name|" + "information_schema.columns|table_schema|union|where|select|delete|update|orderhaving|having|by|count|*|truncate|like"; private const string SqlSeparatKeywords = "'|;|--|\'|\"|/*|%|#"; private static readonly List<string> SqlKeywordsArray = new List<string>(); /// <summary> /// 是否安全 /// </summary> /// <param name="input">輸入</param> /// <returns>返回</returns> public bool IsSafetySql(string input) { if (string.IsNullOrWhiteSpace(input)) { return true; } input = HttpUtility.UrlDecode(input).ToLower(); foreach (var sqlKeyword in SqlKeywordsArray) { if (input.IndexOf(sqlKeyword, StringComparison.Ordinal) >= 0) { return false; } } return true; } /// <summary> /// 返回安全字符串 /// </summary> /// <param name="input">輸入</param> /// <returns>返回</returns> public string GetSafetySql(string input) { if (string.IsNullOrEmpty(input)) { return string.Empty; } if (IsSafetySql(input)) { return input; } input = HttpUtility.UrlDecode(input).ToLower(); foreach (var sqlKeyword in SqlKeywordsArray) { if (input.IndexOf(sqlKeyword, StringComparison.Ordinal) >= 0) { input = input.Replace(sqlKeyword, string.Empty); } } return input; } } }
然后是過濾器,先上代碼
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Test
{
/// <inheritdoc>
/// <cref></cref>
/// </inheritdoc>
/// <summary>
/// SQL注入過濾器
/// </summary>
public class AntiSqlInjectFilter : ActionFilterAttribute
{
/// <inheritdoc />
/// <summary>
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuting(HttpActionContext filterContext)
{
base.OnActionExecuting(filterContext);
var actionParameters = filterContext.ActionDescriptor.GetParameters();
var actionArguments = filterContext.ActionArguments;
foreach (var p in actionParameters)
{
var value = filterContext.ActionArguments[p.ParameterName];
var pType = p.ParameterType;
if (value == null)
{
continue;
}
//如果不是值類型或接口,不需要過濾
if (!pType.IsClass) continue;
if (value is string)
{
//對string類型過濾
filterContext.ActionArguments[p.ParameterName] = AntiSqlInject.Instance.GetSafetySql(value.ToString());
}
else
{
//是一個class,對class的屬性中,string類型的屬性進行過濾
var properties = pType.GetProperties();
foreach (var pp in properties)
{
var temp = pp.GetValue(value);
if (temp == null)
{
continue;
}
pp.SetValue(value, temp is string ? AntiSqlInject.Instance.GetSafetySql(temp.ToString()) : temp);
}
}
}
}
}
}
思路是,加過濾器繼承ActionFilterAttribute,重寫OnActionExecuting方法,獲取入參,對入參中的string類型的所有數據進行過濾。兩種情況,一是參數是string類型,二是類的屬性。過濾器搞定。
過濾器有兩種使用方式,一種是在具體的方法上添加
[HttpPut,Route("api/editSomething")] [AntiSqlInjectFilter] public async Task<bool> EditSomeThingAsync([FromBody]SomeThingmodel) { var response = await SomeThingBusiness.Editsync(model); return response; }
一種是全局配置,在WebApiConfig.cs文件中的Register方法中加上過濾器
using System.Web.Http; namespace Test { /// <summary> /// WebApi配置 /// </summary> public static class WebApiConfig { /// <summary> /// 注冊配置服務 /// </summary> /// <param name="config"></param> public static void Register(HttpConfiguration config) { // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); //全局配置防止SQL注入過濾 config.Filters.Add(new AntiSqlInjectFilter()); } } }
測試有效。
