隐藏

适用于WebApi的SQL注入过滤器

发布:2022/6/29 10:57:46作者:管理员 来源:本站 浏览次数:839

最有效的防止SQL注入的方式是调用数据库时使用参数化查询。

但是如果是接手一个旧的WebApi项目,不想改繁多的数据库访问层的代码,应该如何做。


我的解决方案是加一个过滤器。


先写过滤方法,上代码


Copy

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;

       }

   }

}


然后是过滤器,先上代码


Copy

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类型,二是类的属性。过滤器搞定。


过滤器有两种使用方式,一种是在具体的方法上添加


Copy

[HttpPut,Route("api/editSomething")]

[AntiSqlInjectFilter]

public async Task<bool> EditSomeThingAsync([FromBody]SomeThingmodel)

       {

           var response = await SomeThingBusiness.Editsync(model);

           return response;

       }


一种是全局配置,在WebApiConfig.cs文件中的Register方法中加上过滤器


Copy

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());

       }

   }

}


测试有效。