真正有效的防护只有参数化查询,检测函数仅可作辅助粗筛;必须对LIKE通配符转义、ORDER BY字段白名单校验,动态表名列名需正则+元数据双重验证。

SQL 注入检测函数不能替代参数化查询
直接写一个“检测 SQL 注入”的函数,本质上是危险且无效的。Go 里没有可靠的正则或规则能覆盖所有绕过手法(比如 /**/UNION/**/SELECT、宽字节、编码混淆、注释变体),任何基于字符串匹配的 isSqlInjection 函数都会漏报或误报。真正有效的防护只有在执行前杜绝拼接——用 database/sql 的占位符 + Query/Exec 参数传入,让驱动处理转义。
如果必须做前置校验,只针对已知高危模式做粗筛
某些场景(如日志审计、WAF 规则、管理后台输入预检)需要快速拒绝明显恶意输入。此时可对用户输入做轻量级黑名单扫描,但必须明确:这只是辅助手段,不能用于放行决策。
- 只检查常见关键字(
UNION、SELECT、INSERT、UPDATE、DELETE、DROP、EXEC)——注意大小写不敏感,且需避免误伤(如用户名含select) - 检查注释符号:
--、/*、*/出现在非引号内位置(简单起见,可跳过引号解析,仅作提示) - 检查单引号/双引号不成对(
strings.Count(input, "'")%2 != 0),但注意 JSON 字符串等合法场景会误报 - 永远不返回
true表示“安全”,只返回true表示“高度可疑”,并记录告警
示例(仅示意逻辑,不推荐部署):
func suspectSQLInjection(input string) bool {
input = strings.TrimSpace(input)
if len(input) == 0 {
return false
}
// 简单关键字匹配(不区分上下文)
keywords := []string{"union", "select", "insert", "update", "delete", "drop", "exec", "declare", "xp_"}
<pre class="brush:php;toolbar:false;">for _, kw := range keywords {
if strings.Contains(strings.ToLower(input), kw) {
return true
}
}
// 检查孤立的单引号(可能被用于闭合)
if strings.Count(input, "'")%2 == 1 {
return true
}
// 检查 -- 开头或 /* ... */ 块(极简版)
if strings.HasPrefix(strings.TrimSpace(input), "--") ||
strings.Contains(input, "/*") || strings.Contains(input, "*/") {
return true
}
return false}
真正该做的:强制使用 database/sql 参数化查询
所有用户输入都必须通过 Query 或 Exec 的参数列表传入,而不是拼进 SQL 字符串。Go 的标准库默认支持 ? 占位符(MySQL)或 $1(PostgreSQL),驱动负责底层转义和类型绑定。
- ✅ 正确:
db.Query("SELECT * FROM users WHERE name = ?", userName) - ❌ 错误:
db.Query("SELECT * FROM users WHERE name = '" + userName + "'") - 字符串类型参数无需手动加引号;数字、布尔等类型也由驱动自动处理,不要
fmt.Sprintf拼接 - 批量操作用
sql.Named或循环调用Exec,别用IN (?)拼多个问号
容易忽略的边界点:LIKE 子句和 ORDER BY
这两个地方常被遗漏——它们不支持参数化占位符,但又必须接受用户输入。
-
LIKE中的通配符%和_需要转义:db.Query("SELECT * FROM posts WHERE title LIKE ? ESCAPE '\'", "\%"+title+"\%"),并在LIKE子句中用ESCAPE '\' -
ORDER BY字段名不能参数化,必须白名单校验:validSortFields := map[string]bool{"created_at": true, "title": true},再判断if !validSortFields[userInput] { return errors.New("invalid sort field") } - 动态表名、列名一律禁止来自用户输入;若真无法避免,必须严格正则匹配(如
^[a-zA-Z_][a-zA-Z0-9_]*$)并查 Schema 元数据确认存在
最麻烦的地方从来不是写个检测函数,而是把所有 SQL 构建路径都收口到参数化 + 白名单 + 元数据验证这三道关卡上。漏掉任意一环,检测函数就只是安慰剂。

















