云开发中 db.collection().where() 本身防 SQL 注入,但直连数据库、字符串拼接、动态字段未校验等行为会引入风险;须禁用模板字面量拼接、强制参数化查询、白名单限制 orderBy/field/limit、校验数据归属权并关闭前端 openid 依赖。

db.collection().where() 本身不触发 SQL 注入,但只要你绕开它去直连数据库、拼接字符串、或把用户输入当字段名用,风险立刻回来——修复不是“加个过滤”就完事,而是得先定位你到底在哪条链路上开了口子。
检查云函数里有没有直连 MySQL/PostgreSQL 驱动
这是最常被忽略的重灾区:开发者以为用了云开发就安全了,结果在某个云函数里偷偷 require('mysql2') 或 require('pg'),再用模板字面量拼查询。
- 典型危险写法:
query(`SELECT * FROM orders WHERE user_id = ${event.openid}`)—— 即使openid看似可信,一旦被伪造或劫持(比如未开启「强制校验」),攻击者就能塞进' OR 1=1 -- - 正确做法:改用参数化查询,例如
pool.query('SELECT * FROM orders WHERE user_id = ?', [event.openid]) - 更稳妥的替代:如果只是查自己库,优先退回
db.collection('orders').where({ openid: event.openid }).get(),别自己造轮子
警惕 orderBy()、field()、limit() 这类动态参数
云开发 API 允许传入字段名或数字,但没做白名单校验时,攻击者可以把 event.sortBy 设为 "status; DROP TABLE logs; --",虽然不会执行 DROP(底层非 SQL 解析),但可能触发服务端异常、日志泄露甚至拒绝服务。
- 必须白名单过滤排序字段:
const validSortFields = ['created_at', 'amount', 'status']; if (!validSortFields.includes(event.sortBy)) throw new Error('Invalid sort field'); -
limit()和skip()要设硬上限,比如Math.min(event.limit || 20, 100),防暴力遍历 - 避免把用户传来的
field直接塞进db.collection().field({ [event.field]: 1 }),先校验是否在预设字段列表内
验证所有 where 条件里的字段归属权
云开发不防越权,只防注入。一个 where({ id: event.id }) 查询,如果没校验该 id 是否属于当前用户,就是平行越权漏洞——这不是 SQL 注入,但危害等同,且常和注入点共存。
- 关键动作:在
where前加归属判断,例如if (event.id && !await isUserOwnOrder(event.openid, event.id)) throw new Error('Permission denied'); - 别依赖前端传来的
openid,必须从event.userInfo.openid(云开发登录态)取,且确认云函数已开启「强制校验」开关 - 批量操作更要小心:
db.collection('orders').where({ status: 'pending' })如果没加openid约束,任何用户都能刷出全量待处理订单
别让 JSON 输入变成 SQL 字符串中转站
有些业务需要支持复杂查询,于是开发者把用户传来的 JSON 当条件对象解析,再手动拼成 SQL —— 这等于亲手打开注入大门。
- 反例:
const { field, value } = event.condition; query(`WHERE ${field} = '${value}'`)——value里藏"'; DROP TABLE users; --"就生效 - 正解:要么坚持用云开发原生
where(),要么用 ORM(如 TypeORM)+ 参数化,绝不用字符串拼接 - 若真需动态字段,用映射表转换:
const fieldMap = { name: 'user_name', phone: 'mobile' }; const realField = fieldMap[event.field] || 'id';,再参与查询
真正难的不是写对一行 query,而是每次拿到用户输入时,都得问一句:这个值会进 SQL 吗?会当字段名用吗?会控制查询范围吗?漏掉任意一环,防护就形同虚设。



















