开发工具: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类型,二是类的属性。过滤器搞定。
过滤器有两种使用方式,一种是在具体的方法上添加
[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()); } } }
测试有效。