sql注入防护核心是参数化查询、权限最小化和输入白名单校验,而非多因素认证;orm不自动防注入,dba需关闭危险数据库配置。

SQL注入防护不能靠“强制多因素认证”解决
多因素认证(MFA)保护的是用户登录环节,不是SQL执行环节。把MFA加到数据库连接层,对SELECT * FROM users WHERE name = 'admin' OR '1'='1'这类注入完全无效——请求进来时,用户早已通过认证,SQL语句本身才是攻击载荷。
真正防SQL注入的三个落地动作
防注入的核心是「参数化」+「权限最小化」+「输入不信任」,不是加认证开关。
- 所有动态拼接的查询必须用
PreparedStatement(Java)、pg_query_params(PHP/PgSQL)、cursor.execute(sql, params)(Python/psycopg2)等参数化接口,绝不用string.format或+拼SQL - 数据库账号按业务隔离:查用户列表的服务只给
SELECT权限,且限定在user_view视图,不给users基表访问权 - 前端传来的
id、sort_by、limit等字段,后端必须白名单校验:sort_by只能是['created_at', 'name'],limit必须int且≤100
为什么ORM也不能自动防注入
ORM只是封装了参数化逻辑,但留了裸SQL入口。Django的raw()、SQLAlchemy的text()、MyBatis的${}(非#{})都会绕过参数化。
-
queryset.filter(name__icontains=request.GET.get('q'))是安全的 -
Model.objects.raw("SELECT * FROM app_user WHERE name LIKE '%" + q + "%'")直接中招 -
session.execute(text("UPDATE logs SET status = :s WHERE id = :id"), {"s": status, "id": id})安全;但{"s": f"'{status}'"}就又拼回去了
DBA侧最容易被忽略的加固点
应用层做对了,DBA还得关掉“默认危险配置”,否则等于门锁了还留着窗。
- 禁用
pg_stat_statements以外的统计插件(如auto_explain若开启log_min_duration_statement=0,可能泄露SQL结构) - PostgreSQL里关闭
track_activities和track_counts(除非真需要监控),避免pg_stat_activity暴露未脱敏的查询文本 - MySQL 8.0+ 必须设
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,否则'1 OR 1=1'可能被截断成'1'后静默执行
防注入不是加一层认证就能交差的事,是每个SQL调用点、每个数据库账号、每个配置项都得对齐的细节活。最常出问题的地方,恰恰是“以为ORM兜底了”和“DBA觉得应用层该管好”的中间地带。










