url参数本身不构成sql注入风险,真正危险的是后端把解码后的参数值直接拼进sql语句;必须走参数化查询,数字型需parseint()校验,字符串型不可靠正则过滤,动态字段名/表名须白名单控制,in子句要动态生成占位符,orm的raw调用会绕过防护。

URL参数本身不构成SQL注入风险,真正危险的是后端把解码后的参数值直接拼进SQL语句。
URL参数解码后必须走参数化查询
浏览器地址栏里看到%27或中文,不代表安全;服务端调用decodeURIComponent()或框架自动解码后,得到的字符串仍是不可信输入。它和表单提交、JSON body里的数据一样,必须进参数化查询流程。
- 错误做法:
const id = decodeURIComponent(req.query.id); db.query(`SELECT * FROM users WHERE id = ${id}`) - 正确做法:
const id = parseInt(req.query.id, 10); db.query("SELECT * FROM users WHERE id = ?", [id]) - 数字型参数别忘了类型转换——
parseInt()或Number()能过滤掉1%27 OR 1=1这类混入的字符串 - 字符串型参数不能靠正则“删单引号”来防注入,那只是掩耳盗铃;唯一有效的是占位符+驱动层绑定
动态字段名/表名无法参数化,必须白名单校验
URL里常见?sort=name&order=desc或?table=logs这种需求,但ORDER BY ?或FROM ?在绝大多数驱动里不被支持——数据库语法解析器要求字段名、表名是字面量,不是参数。
- 白名单硬编码:
if (!['name', 'created_at'].includes(sort)) throw new Error('Invalid sort field') - 映射表替代拼接:
const fieldMap = { name: 'users.name', created_at: 'users.created_at' }; const sql = `ORDER BY ${fieldMap[sort]}` - 绝对不要写
ORDER BY ${sort}或FROM ${table},哪怕你做过escape()或replace(/[^a-z_]/g, '') - PostgreSQL中
data->>'${key}'里的key也属此类,必须白名单控制
IN子句参数数量不确定时要动态生成占位符
URL传?ids=1,2,3很常见,但WHERE id IN (?)只能匹配单个值,WHERE id IN (?, ?, ?)又得根据数组长度实时构造SQL。
- Node.js示例:
const placeholders = ids.map(() => '?').join(','); db.query(`SELECT * FROM users WHERE id IN (${placeholders})`, ids) - Python示例:
placeholders = ','.join(['?'] * len(ids)); cursor.execute(f"SELECT * FROM users WHERE id IN ({placeholders})", ids) - 别用
WHERE id IN (" + ids.join(',') + ")"——这是典型拼接陷阱 - ORM如Django的
filter(id__in=ids)默认安全,但Sequelize的raw()会绕过防护
最易被忽略的点:日志记录、审计查询、缓存键生成这些“非主业务路径”,常把已解码的URL参数再次拼进SQL。只要数据落地前那一行execute没走参数化,前面所有编码、校验、白名单都归零。











