SQL 注入防护需结合参数化查询、白名单校验动态结构、ORM 安全用法、数据库权限控制及 Dual Auth;授权须行级隔离、字段粒度控制;组合漏洞需通过日志监控与自动化测试发现。

SQL 注入防护:别只靠参数化查询就以为安全了
参数化查询是防 SQL 注入的底线,但不是全部。很多团队加了 PreparedStatement 或query.bind()就松口气,结果在拼接表名、排序字段、动态 WHERE 条件时又裸写user_input,等于把门锁了却把钥匙焊在门把手上。
常见错误现象:SELECT * FROM users ORDER BY ? —— ? 在 ORDER BY 里不生效,最终变成字符串拼接;或者用 format("SELECT …… WHERE type = '%s'", user_type) 绕过 ORM 的绑定逻辑。
- 动态表名 / 列名必须白名单校验,例如只允许
["users", "orders"]和["created_at", "status"],禁止任何形式的用户输入直接进 SQL 结构 - ORM 如 Django 的
order_by()、SQLAlchemy 的text()需配合bindparam(),不能只靠.filter(User.name == request.args['name'])——后者在复杂表达式里可能被绕过 - 数据库层面启用
sql_mode=STRICT_TRANS_TABLES(MySQL)或最小权限原则(如应用账号禁用DROP、CREATE),让注入即使成功也无法执行高危操作
Dual Auth:为什么 2FA 不能只做登录环节
双重身份验证常被当成“登录时输个验证码”,但关键数据操作(如转账、删库、改密码)若没二次确认,等于给已登录的会话开了后门。攻击者拿到 session cookie 或 JWT 后,所有敏感操作畅通无阻。
使用场景:修改邮箱、导出全量用户数据、切换管理员角色、重置他人密码。
- 敏感操作前强制触发 2FA,且验证 token 必须绑定本次操作上下文(比如
action=delete_user&target_id=123),不能复用登录时的通用 token - 避免用短信作为唯一 2FA 通道——SIM 劫持已成常见攻击路径;优先用 TOTP(如
pyotp生成)或 WebAuthn - 后端验证时检查
auth_time(OAuth2 标准字段)或自定义2fa_valid_until时间戳,防止 token 长期有效
授权绕过:RBAC 模型里最容易漏掉的三个点
角色权限控制(RBAC)常因“功能上线急”被简化成if user.role == 'admin',但真实业务中存在数据级隔离、临时权限、跨租户边界等场景,硬编码判断必然失效。
常见错误现象:GET /api/users/123接口只校验user.role == 'admin',没校验user.tenant_id == target_user.tenant_id,导致 SaaS 平台客户数据互相可见。
- 读操作必须做行级权限检查(Row-Level Security),PostgreSQL 可用
RLS POLICY,MySQL 需在 WHERE 中显式加AND tenant_id = ? - 写操作要区分“谁创建的”和“谁能改”——比如工单系统里,客服可改状态但不能删原始描述,需按字段粒度控制,而非整个
UPDATE - 避免前端传来的
role字段参与权限决策,所有判断必须基于后端查出的用户实体(如user_db.roles),防止篡改请求体中的{"role":"superadmin"}
组合漏洞:当 SQL 注入遇上弱授权检查
单独看,参数化查询和 RBAC 都合规;但合起来可能崩盘。比如一个搜索接口用 WHERE name LIKE ?,但? 值来自用户可控的 search_term,而该接口又未校验调用者能否查看目标数据表——攻击者构造search_term=%' OR 1=1 -- 就能绕过 WHERE 过滤,再结合未限制的 SELECT * 直接拖库。
性能影响:过度校验(如每次请求都查 N 次权限表)会拖慢响应,但更危险的是为性能砍掉关键校验。
- 所有 API 入口必须同时完成认证(Authentication)、授权(Authorization)、输入净化(Sanitization)三步,缺一不可
- 日志里记录
user_id、endpoint、affected_rows(非敏感数据),而不是只记"success"——异常高频的SELECT COUNT(*)可能是探测行为 - 测试时用
sqlmap -u "http://x/api/search?q=1" --auth-type=cookie --auth-cred="session=xxx"跑一遍,比人工 Code Review 更容易暴露组合缺陷
最麻烦的从来不是单点漏洞,而是两个看似合规的模块,在边界处悄悄取消了彼此的约束。检查时得盯着接口之间怎么传数据,而不是只看每个函数自己干得漂不漂亮。