You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DevExpress Grid远程筛选构建WHERE条件时如何防范SQL注入

问题风险说明

你当前的实现存在极高的SQL注入风险:

  • 前端拼接SQL片段的逻辑完全不可信,用户可以随意篡改前端传入的参数内容,构造恶意SQL语句
  • 存储过程中直接将传入的@SearchParam字符串拼接进动态SQL执行,没有任何过滤校验,攻击者可以通过构造恶意参数实现删表、拖库等操作
  • 即便是@Skip、@Take这类你认为是数字的参数,直接字符串拼接也存在潜在风险
可落地的防护方案

不要信任任何来自前端的输入,所有SQL语法相关的拼接、校验逻辑必须放在服务端完成,核心遵循「白名单校验+全参数化查询」两个原则:

  • 废弃前端拼接SQL片段的逻辑:前端只需要传结构化的筛选规则(字段名、操作符、筛选值、逻辑连接符),绝对不要向前端传递拼好的SQL字符串
  • 服务端做严格白名单校验
    • 预定义所有允许被筛选的字段集合,凡是筛选条件里的字段名不在集合内的直接拦截返回错误
    • 预定义所有允许使用的操作符集合(contains、notcontains、startswith、endswith、等于、大于、小于等),不在集合内的操作符直接拦截
    • 预定义允许的逻辑连接符,仅支持AND/OR,其余连接符直接拦截
  • 全链路使用参数化查询,所有用户传入的筛选值、分页参数都作为SQL参数传递,绝对不要直接拼接到SQL字符串中
参考实现(C# + Dapper 层动态构建查询,废弃原有存储过程拼接逻辑)

不需要在存储过程中拼接动态SQL,直接在C#端构建安全的查询语句即可:

// 1. 定义白名单
var allowedFilterFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
    "ColumnA", "ColumnB", "ColumnC"
};
var allowedOperators = new Dictionary<string, Func<object, object>>(StringComparer.OrdinalIgnoreCase)
{
    ["contains"] = (val) => $"%{val}%",
    ["notcontains"] = (val) => $"%{val}%",
    ["startswith"] = (val) => $"{val}%",
    ["endswith"] = (val) => $"%{val}",
    ["eq"] = (val) => val,
    ["neq"] = (val) => val,
    ["gt"] = (val) => val,
    ["lt"] = (val) => val
};
var operatorSqlMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    ["contains"] = "{0} LIKE @{1}",
    ["notcontains"] = "{0} NOT LIKE @{1}",
    ["startswith"] = "{0} LIKE @{1}",
    ["endswith"] = "{0} LIKE @{1}",
    ["eq"] = "{0} = @{1}",
    ["neq"] = "{0} <> @{1}",
    ["gt"] = "{0} > @{1}",
    ["lt"] = "{0} < @{1}"
};

// 2. 解析前端传的结构化筛选条件,前端传参格式改为:[{Field:"ColumnA", Operator:"contains", Value:"ABC", Logic:"AND"}, ...]
var filterConditions = 从前端请求拿到的结构化筛选列表;
var whereClauseBuilder = new List<string>();
var parameters = new DynamicParameters();
var paramIndex = 0;

foreach (var condition in filterConditions)
{
    // 字段名校验
    if (!allowedFilterFields.Contains(condition.Field))
        throw new ArgumentException("不支持的筛选字段");
    // 操作符校验
    if (!allowedOperators.ContainsKey(condition.Operator))
        throw new ArgumentException("不支持的筛选操作符");
    // 生成参数名、处理参数值
    var paramName = $"p{paramIndex++}";
    var paramValue = allowedOperators[condition.Operator](condition.Value);
    parameters.Add(paramName, paramValue);
    // 拼接当前条件的SQL片段
    whereClauseBuilder.Add(string.Format(operatorSqlMap[condition.Operator], condition.Field, paramName));
}

// 3. 拼接最终SQL
var baseSql = "SELECT ColumnA, ColumnB, ColumnC FROM dbo.TestTable";
if (whereClauseBuilder.Any())
{
    baseSql += " WHERE " + string.Join(" AND ", whereClauseBuilder);
}
// 分页参数也做参数化,不要直接拼接
baseSql += " ORDER BY ColumnA ASC OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY;";
parameters.Add("Skip", skip, DbType.Int32);
parameters.Add("Take", take, DbType.Int32);

// 4. 用Dapper执行查询
var result = connection.Query<TestTableModel>(baseSql, parameters);

如果你必须保留存储过程的实现,不要传入拼接好的SQL字符串,而是将结构化筛选条件作为表值参数传入存储过程,在存储过程内完成同样的白名单校验,通过sp_executesql的参数化能力传入所有筛选值,禁止直接拼接用户传入的内容到SQL语句中。

注意:不要尝试通过转义单引号、替换关键字的方式做黑名单防护,这类防护非常容易被绕过,只有白名单+全参数化是可靠的防护方式。

内容的提问来源于stack exchange,提问作者Ahad Porkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:42:29