Beego ORM 默认安全,因QuerySeter方法均采用预编译参数绑定,SQL模板与值分离;危险在于绕过ORM使用raw SQL拼接用户输入。

Beego 本身不自动防 SQL 注入,但只要不用 raw SQL 拼接 + 不手动构造查询字符串,就基本不会中招。 它的 ORM 和参数化查询机制是安全的,问题出在开发者绕过这些机制时。
Beego ORM 的 QuerySeter 方法为什么默认安全
QuerySeter(如 Filter、OrderBy、Limit)底层全部走预编译参数绑定,SQL 模板和值分离。比如:
o.QueryTable("user").Filter("name", nameInput).Filter("status", "active")生成的是类似 SELECT * FROM user WHERE name = ? AND status = ? 的语句,nameInput 被当作参数传入,不会参与 SQL 解析。
- 所有
Filter、Exclude、OrderBy、Limit、Offset都安全,包括带双下划线的操作符(status__in、created__gt) - 字段名(如
"name")是硬编码或白名单校验过的,不允许运行时传入;值(如nameInput)一律参数化 - 如果传了空字符串或
nil,ORM 会跳过该条件,不会拼出WHERE name = ''这类脆弱逻辑
哪些写法会直接触发 SQL 注入
真正危险的是显式放弃 ORM 安全层,用字符串拼接构造 SQL——尤其是把用户输入塞进 Raw() 或 QueryRow() 的 SQL 字符串里。
-
o.Raw("SELECT * FROM user WHERE name = '" + name + "'")—— 经典拼接,name = "admin' OR '1'='1"直接生效 -
o.Raw("UPDATE user SET status = ? WHERE id IN (" + ids + ")")——ids是逗号分隔字符串,无法参数化整个 IN 列表 - 用
fmt.Sprintf拼接表名/字段名:o.Raw(fmt.Sprintf("SELECT * FROM %s", tableName)),哪怕加了sql.Escape也不可靠 - 从 URL 路径或 query 中提取字段名后直接用于
Filter第一个参数:o.Filter(fieldName, value),若fieldName未白名单校验,可能绕过字段约束
IN 查询和动态字段怎么安全处理
IN 查询不能靠单个 ? 占位符解决,必须动态生成占位符并绑定对应数量的参数;字段名/表名则必须来自白名单,不可由前端控制。
- IN 安全写法:先用
strings.Repeat("?,", len(ids)-1) + "?"构造占位符串,再用o.Raw(sql, ids...)批量传参 - 动态字段名(如排序字段)必须限制在固定集合内:
validSortFields := map[string]bool{"id": true, "name": true, "created": true},检查通过才允许进OrderBy - 如果真要支持任意字段过滤(如后台搜索框),应提前定义好字段映射表,把前端传的
field=user_name映射为内部字段"name",再进Filter - 避免用
GetString直接取字段名:field := ctx.GetString("field")→o.Filter(field, val)是高危模式
Raw SQL 必须带参数绑定且禁用字符串插值
Beego 的 Raw() 支持参数绑定,但很多人只记得写 SQL,忘了传参——这是线上最常被忽略的点。
- 错误:
o.Raw("SELECT * FROM user WHERE role = '" + role + "'") - 正确:
o.Raw("SELECT * FROM user WHERE role = ?", role)或o.Raw("SELECT * FROM user WHERE role = ? AND status = ?", role, status) - 多行 SQL 可用反引号包裹,但参数仍需一一对应,不能少传、错序
- 如果 SQL 复杂到难以参数化(比如动态 UNION、CTE),宁可拆成多个简单查询,也不要妥协拼接
真正难防的不是技术漏洞,而是“以为 ORM 安全就啥都敢传”的心理——只要出现一次 Raw + 用户输入拼接,整套防护就归零。字段白名单、IN 占位符生成、参数强绑定,这三件事得刻进肌肉记忆里。


















