Koa2 本身不支持 SQL 查询,需搭配 mysql2、pg 等驱动执行 Raw SQL;mysql2.query() 仅对 ? 占位符参数自动转义,字符串拼接或模板字面量均导致 SQL 注入;pg.client.query() 强制参数化,仅接受 $1 形式占位符或命名参数;ORM 如 Knex 仅在链式调用中自动防护,knex.raw() 仍需手动传参防注入。

Koa2 本身不提供 SQL 查询能力,所谓“在 Koa2 中使用 Raw SQL”实际是搭配 mysql2、pg 或 sqlite3 等底层驱动直接执行 SQL —— 这些驱动默认不做自动转义,必须手动处理,否则就是 SQL 注入高危路径。
为什么 mysql2 的 query() 不自动转义参数?
因为 mysql2.query() 有两个行为模式:传入字符串时直接拼接(不转义),传入数组或对象时才启用占位符解析。很多人只写 query('SELECT * FROM users WHERE id = ' + userId),这等于把用户输入裸奔进 SQL。
-
query('SELECT * FROM users WHERE name = ?', ['O''Reilly'])→ 安全,?占位符触发内部 escape -
query(`SELECT * FROM users WHERE name = '${name}'`)→ 危险,模板字符串绕过所有保护 -
query('SELECT * FROM users WHERE id = ' + req.query.id)→ 危险,数字看似安全,但id=1 OR 1=1仍可注入
pg 的 client.query() 怎么避免手动拼接?
PostgreSQL 驱动 pg 要求显式使用参数化查询,不支持字符串拼接执行。它用 $1、$2 占位符,且只接受数组或命名对象传参 —— 这其实是更严格的安全设计,但容易因写错格式导致 error: bind message supplies 0 parameters, but prepared statement "" requires 1。
- 正确:
client.query('SELECT * FROM posts WHERE slug = $1', [slug]) - 错误:
client.query(`SELECT * FROM posts WHERE slug = '${slug}'`)(会报语法错或注入) - 命名参数也行:
client.query('SELECT * FROM posts WHERE slug = $(slug)s', { slug }),但注意语法是$(slug)s不是:slug
ORM(如 knex)能帮你绕过手动转义吗?
可以,但前提是不用 knex.raw()。一旦调用 knex.raw('UPDATE users SET name = ? WHERE id = ?', [name, id]),你就又回到手动管理参数的位置;而 knex('users').where('id', id).update({ name }) 这类链式调用,底层才真正屏蔽了 SQL 构建细节。
-
knex.raw()是逃生舱口,不是常规通道 —— 它不校验 SQL 结构,也不拦截恶意字符串 - 即使使用
knex.raw(),也必须把所有外部输入塞进第二个参数数组,不能插进字符串里 - 注意
knex默认开启escape,但仅对它自己生成的语句生效;raw()中的??(标识符)和?(值)仍需你配对填写,错一个就失效
真正容易被忽略的点是:很多开发者以为“用了 ORM 就安全”,结果在日志埋点、动态排序字段、复杂 UNION 查询里偷偷摸摸拼接 raw() 字符串,还加个 console.log 就当测试过了 —— 这类地方最常爆 SQLITE_ERROR: near "OR": syntax error 或更糟的静默数据篡改。

















