string.Replace不能防SQL注入,因其仅做文本替换,无法解决语义污染问题,且易漏Unicode变体、破坏正常数据、挡不住堆叠注入等;正确做法是全程使用参数化查询。

为什么 string.Replace 不能防 SQL 注入
因为替换只是文本处理,而 SQL 注入的本质是语义污染——攻击者靠拼接出合法但恶意的 SQL 语法来绕过检查。string.Replace 对 ' 或 -- 的简单替换,既漏掉 Unicode 变体(如全角单引号)、又破坏正常数据(比如用户真名叫 O'Reilly),还挡不住联合查询、堆叠注入等高级手法。
常见错误现象:cmd.CommandText = "SELECT * FROM users WHERE name = '" + input.Replace("'", "''") + "'"; 看似“转义”了,但遇到 ; DROP TABLE users-- 依然执行第二条命令。
- 永远别在拼接前做“关键词过滤”或“字符替换”,这属于防御性幻觉
- 所有用户输入进入 SQL 语句前,必须走参数化路径,不区分字段类型、不看输入内容
- ORM 如 Entity Framework 的
.Where(x => x.Name == input)默认走参数化,但手写.FromSqlRaw就立刻退化回风险区
如何正确用 SqlCommand.Parameters 绑定值
核心就一条:值进 SQL,只通过 Parameters.Add 或 Parameters.AddWithValue,且绝不拼进 CommandText 字符串里。
使用场景:增删改查所有含用户输入的语句,包括 IN 子句、ORDER BY 后的字段名(注意:字段名不能参数化,需白名单校验)。
参数差异:AddWithValue 自动推断类型,但对 NULL 或空字符串可能误判为 varchar(1) 引发隐式转换;生产环境建议用 Add("@name", SqlDbType.NVarChar).Value = input 显式指定。
var cmd = new SqlCommand("SELECT * FROM products WHERE category = @cat AND price <h3>
<code>ExecuteScalar</code> 和 <code>ExecuteReader</code> 也要防注入吗</h3><p>要。只要 SQL 字符串里混入了未消毒的用户输入,不管用哪个执行方法,漏洞都存在。区别只在于攻击后果不同:<code>ExecuteScalar</code> 可能被用来偷单个字段(如密码哈希),<code>ExecuteReader</code> 更容易拖库。</p><p>容易踩的坑:</p>
- 以为“只读查询就安全”,结果写成
"SELECT * FROM logs WHERE ip = '" + Request.UserHostAddress + "'" - 在存储过程中拼接动态 SQL(如
EXEC('SELECT * FROM ' + @table)),此时需用sp_executesql+ 参数,而非EXEC - 把参数化当成“万能胶”,却在同一条语句里混用:参数化一部分、拼接另一部分(如表名拼接 + 字段值参数化)
为什么 ORM 不等于自动免疫
Entity Framework、Dapper 这类工具默认对 .Where()、.Query<t>()</t> 等接口做参数化,但它们不拦截你主动写的原始 SQL。
典型翻车点:
- Dapper 的
connection.Query("SELECT * FROM users WHERE id = " + id)—— 没有@符号,就是裸拼 - EF Core 的
context.Users.FromSqlRaw("SELECT * FROM users WHERE name LIKE '%" + keyword + "%'")——FromSqlRaw不处理参数,FromSqlInterpolated才安全 - 用 EF 动态构建表达式树时,误把用户输入塞进
Expression.Constant当作字段名,实际应走白名单映射
最常被忽略的复杂点:多层封装后,SQL 构建逻辑可能散落在服务层、仓储层甚至自定义扩展方法里,人眼很难追踪哪一行悄悄拼了字符串。只要没看到 @ 占位符和显式 Parameters 添加,就得怀疑。










