net 7 中间件sql 注入的方法

百度一下都是 filter 防止sql 注入的,其实到MVC 的fileter 已经浪费了很多性能,我们在管道组装的时候,就可以拦截非法字符。在中间件收集参数是比较麻烦的事情,、

知识点储备需要理解中间件,以及怎么封装中间件,我们netcore 都是add 一个服务+app.use 一个中间件开发方式  2 如果收集post 请求的参数,这个和filer 不一样  需要流的方式收集。如果还有其他办法,大家一起讨论一下 QQ 53262607 

首先我定义了几个类,充当角色说明一下 

 ISQLFormat.cs 定义接口,用来处理核心注入逻辑

SQLFormat.cs  实现类,用来具体实现

FilterSQLString.cs    optoins  预设需要过滤的非法字符串和 特殊符号

SendMail.cs               optoins   邮件内容

sqlMail                 optoins   发送邮件的配置

 

AddSQLFromateExtensions.cs    IOC 注入的需要的类

SqlFilterRequestMiddlewareExtensions.cs  管道中间件封装

SQLFormatMiddleWare.cs  中间件处理过程

 

结构如下

 

 

ISQLFormat.cs


 public interface ISQLFormat
    {
        /// <summary>
        /// 获取post 和get 的 所有参数 收集参数存放字典
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        Dictionary<string, string> FromQueryString(HttpRequest request);
    
        /// <summary>
        /// 校验参数和预定义的非法字符串比较 输出哪些是非法字符
        /// </summary>
        /// <param name="dic"></param>
        /// <param name="sql"></param>
        /// <returns></returns>
        string Checksqlstring(Dictionary<string, string> dic, string sql);
      
        /// <summary>
        /// options  当遇到非法sql 发送预警邮件
        /// </summary>
        /// <param name="sendMail"></param>
        /// <param name="sqlMail"></param>
        /// <returns></returns>
        bool SendEmails(SendMail sendMail, sqlMail sqlMail);
       

    }

  SQLFormat.cs

    public class SQLFormat : ISQLFormat
    {
        private readonly ILogger<SQLFormat> _logger;

        public SQLFormat(ILogger<SQLFormat> logger)
        {
            _logger = logger;
        }

        public string Checksqlstring(Dictionary<string, string> dic, string sql)
        {
            string result = string.Empty;
            if (dic.Count() == 0) { return result; }
            if (!string.IsNullOrEmpty(sql))
            {
                string[] arry = sql.Split('|');
                foreach (var item in arry)
                {
                    var a = dic.Where(x => x.Value.IndexOf(item) >= 0).FirstOrDefault();
                    if (!string.IsNullOrEmpty(a.Value))
                    {
                        result += $"item:{item} 和配置文件{a.Value}中的关键词匹配上了sql注入";
                        break;
                    }
                }
            }
            return result;

        }

        public Dictionary<string, string> FromQueryString(HttpRequest request)
        {
            string date = string.Empty;
            {
                if (request.Method.ToLower().Equals("post"))
                {
                    try
                    {
                        request.EnableBuffering();//允许重复读取
                        var reader = new StreamReader(request.Body);
                        date = reader.ReadToEndAsync().Result;//异步
                        request.Body.Position = 0;//重置,后续才能正确读取
                    }
                    catch (Exception)
                    {
                        date = string.Empty;
                    }
                    _logger.LogInformation($"From方法收集到的body 里面信息:{date}");
                    return GetFrom(date);
                }
                else
                {
                    date = request.QueryString.Value;
                    return GetQuery(date); ;
                }
            }
        }
        /// <summary>
        /// 收集post 的参数
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private Dictionary<string, string> GetFrom(string data)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            if (string.IsNullOrEmpty(data))
            {
                _logger.LogInformation($"获取from 提交的data中间件:{data}");
                return dic;
            }
            JObject obj = (JObject)JsonConvert.DeserializeObject(data)!;
            foreach (JProperty item in obj.Properties())
            {
                dic.Add(item.Path, item.Value.ToString());
            }
            return dic;
        }
        /// <summary>
        /// 收集 query 的参数
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private Dictionary<string, string> GetQuery(string data)
        {
            var dic = new Dictionary<string, string>();
            if (data.Contains("&") || data.Contains("="))
            {
                var args = HttpUtility.ParseQueryString(data);
                if (args == null || args.Count <= 0) { return dic; }
                foreach (var key in args.AllKeys)
                {
                    if (key == null) { continue; }
                    if (dic.ContainsKey(key))
                    {
                        dic[key] = args[key]!;
                    }
                    else
                    {
                        dic.Add(key, args[key]!);
                    }
                }
            }
            return dic;
        }
        /// <summary>
        /// 发现非法字符 过滤 并发送邮件
        /// </summary>
        /// <param name="sendMail"></param>
        /// <param name="sqlMail"></param>
        /// <returns></returns>
        public bool SendEmails(SendMail sendMail, sqlMail sqlMail)
        {
            if (sendMail.strTo != "" && sendMail.strBody != "" && sendMail.strSubject != "")
            {
                MailMessage mailMsg = new MailMessage();//实例化对象
                mailMsg.From = new MailAddress(sqlMail.EmailNameForm!, sqlMail.EmailNameFormT);//源邮件地址和发件人
                mailMsg.To.Add(new MailAddress(sendMail.strTo!));//收件人地址
                mailMsg.Subject = sendMail.strSubject;//发送邮件的标题
                StringBuilder sb = new StringBuilder();
                sb.Append(sendMail.strBody);
                mailMsg.Body = sb.ToString();//发送邮件的内容
                                             //指定smtp服务地址(根据发件人邮箱指定对应SMTP服务器地址)
                SmtpClient client = new SmtpClient();//格式:smtp.126.com  smtp.164.com
                client.Host = sqlMail.EmailNameFormIP!;
                //要用587端口
                client.Port = 25;//端口
                                 //加密
                                 //  client.EnableSsl = true;
                                 //通过用户名和密码验证发件人身份
                client.Credentials = new NetworkCredential(sqlMail.EmailServerUser, sqlMail.EmailNameFormPWD); // 
                try
                {
                    client.Send(mailMsg);
                    return true;
                }
                catch (SmtpException ex)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
    }

  

 

FilterSQLString.cs   
public class FilterSQLString
    {
        public string? sqlstring { get; set; }
        public string? sqlparme { get; set; }
    }


SendMail.cs  
   public class SendMail
    {
        /// <summary>
        /// 发件人
        /// </summary>
        public string ?strTo { get; set; }
        /// <summary>
        /// 内容
        /// </summary>
        public string ?strBody { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        public string ?strSubject { get; set; }
    }

sqlMail.cs

    public class sqlMail
    {
        /// <summary>
        /// 发送邮件的基础配置
        /// </summary>
        public string ?EmailNameForm { get; set; }
        public string ?EmailNameFormT { get; set; }
        public string ?EmailNameFormIP { get; set; }
        public string ?EmailNameFormPWD { get; set; }
        public string ?EmailServerUser { get; set; }
    }

 

AddSQLFromateExtensions.cs  封装类

 public static class AddSQLFromateExtensions
    {
        /// <summary>
        /// 传参的
        /// </summary>
        /// <param name="services"></param>
        /// <param name="action"></param>
        public static void AddSQLFroma(this IServiceCollection services, Action<FilterSQLString> action)
        {
          
            services.Configure(action);
            services.AddTransient<ISQLFormat, SQLFormat>();
        }
        /// <summary>
        /// 非传参的
        /// </summary>
        /// <param name="services"></param>
        public static void AddSQLFromaInvoke( this WebApplicationBuilder builder)
        {
            builder.Services.Configure<sqlMail>(builder.Configuration.GetSection("sqlMail"));
            builder.Services.Configure<FilterSQLString>(builder.Configuration.GetSection("FilterSQLString"));
            builder.Services.Configure<sqlMail>(builder.Configuration.GetSection("RedisDistributedCache"));
            builder.Services.AddTransient<ISQLFormat, SQLFormat>();
            builder.Services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
        }
    }

SqlFilterRequestMiddlewareExtensions.cs

  public static class SqlFilterRequestMiddlewareExtensions
    {
        public static IApplicationBuilder SqlFilterRequest(this IApplicationBuilder app)
        {
            return app.UseMiddleware<SQLFormatMiddleWare>();
        }
    }

SQLFormatMiddleWare.cs 请求处理的核心业务类

public class SQLFormatMiddleWare
    {

        private readonly RequestDelegate _next;
        private readonly FilterSQLString _SqlOptions;
        private readonly sqlMail _sqlMail;
        private readonly SendMail _sendmail;
        private readonly ILogger<SQLFormatMiddleWare> _logger;
        private readonly IHttpContextAccessor _httpContext;
        private readonly ISQLFormat _sQLFormat;

        public SQLFormatMiddleWare(RequestDelegate next,
           IOptionsMonitor<FilterSQLString> monitor,
           IOptionsMonitor<SendMail> sendmail,
           ILogger<SQLFormatMiddleWare> logger,
           IOptionsMonitor<sqlMail> sqlMail,
           ISQLFormat sQLFormat,
            IHttpContextAccessor httpContext)
        {
            _next = next;
            _SqlOptions = monitor.CurrentValue;
            _sqlMail = sqlMail.CurrentValue;
            _sendmail = sendmail.CurrentValue;
            _logger = logger;
            _httpContext = httpContext;
            _sQLFormat = sQLFormat;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            //  await _next(context);
            context.Request.EnableBuffering();//允许重复读取
            string Path = context.Request.Path.Value!;
            //   HttpRequest a = context.Request;
            var data = _sQLFormat.FromQueryString(context.Request);
        //  string blackip = _cacheClientDB.Get<string>("blackipcache");///这里调redis 临时写死IP黑名单
             string blackip = "180.66.66.21";//过滤的ip  这里可以用redis 替代 偷懒了 
            string ip = _httpContext.HttpContext!.Connection.RemoteIpAddress!.ToString();
            _logger.LogInformation($"ip:{ip} blackip:{blackip}");
            if (!string.IsNullOrEmpty(blackip) && blackip.Contains(ip))
            {
                context.Response.StatusCode = 403;
                return;
            }
            _logger.LogInformation($"data:{data.Count}");
            var result = _sQLFormat.Checksqlstring(data, _SqlOptions.sqlstring!);
            if (!string.IsNullOrEmpty(result))
            {
                _logger.LogInformation($"result:{result}");
                context.Response.StatusCode = 401;
                _sendmail.strSubject = result;
                bool b = _sQLFormat.SendEmails(_sendmail, _sqlMail);
                _logger.LogInformation($"b:{b} result:{result}");
                return;
            }
            else
            {
                await _next(context); //一下环节
            }

        }
    }

appsettings.json

 

测试如下

postman  post 2个参数

 调试中我们看到 2个post 参数我们都收集到了

注入的字符串 已经过滤出来  

 然后返回401 

 

 postman 也可以观察到 返回值是401  

 

 

posted @ 2023-04-21 16:12  非著名架构师  阅读(213)  评论(0编辑  收藏  举报