SQL注入防御的核心是参数化查询,而非输入过滤;必须使用数据库驱动原生支持的预编译机制,杜绝用户输入进入SQL解析阶段。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 多模态理解力帮你轻松跨越从0到1的创作门槛☜☜☜
SQL注入防御的核心不是过滤,是参数化
用字符串拼接构造 SQL 语句,哪怕加了
或正则替换关键词,都挡不住绕过手段。真正有效的防线只有一条:让用户输入永远不进入 SQL 解析阶段。这意味着必须用数据库驱动原生支持的参数化查询(prepared statement),而不是靠应用层“清理”输入。
常见错误现象:
这类写法,无论后端怎么 trim、strip_tags、甚至 blacklist
,攻击者总能用十六进制编码、大小写混用、注释符变形等方式绕过。
PHP 的
和
是唯一推荐路径;
函数已废弃且不支持参数化
Node.js 用
,不是
Python 的
中的
或
占位符由驱动解析,变量值绝不会被当 SQL 执行
ORM 并不自动免疫 SQL 注入
很多人以为用了 Django ORM 或 Laravel Eloquent 就高枕无忧,其实不然。这些框架在常规链式调用(如
)中确实安全,但一旦出现原始 SQL 拼接或动态字段名/表名,风险立刻回归。
使用场景:需要动态排序字段、多租户分表、或 legacy 系统迁移时临时兼容旧逻辑。
Django 的
、
、
等方法若拼接用户输入,等同于裸写 SQL
Laravel 的
中的
若来自请求参数,就是典型漏洞点
Java MyBatis 的
是字符串替换,
才是预编译参数——这个区别必须刻在脑子里
WHERE 条件里用 IN 时参数数量不确定怎么办
这是参数化最常卡壳的地方:SQL 要求占位符数量固定,但前端可能传回 1 个或 50 个 ID。硬编码
不现实,也不该用字符串拼接生成占位符。
通义千问
阿里巴巴推出的全能AI助手
下载
正确做法是根据输入数组长度动态生成对应数量的
,再把数组整体传入参数列表。不能省略这一步,否则要么性能差(循环单条查),要么又退回到拼接。
PHP PDO 示例:
Python sqlite3 不支持多值占位符,需用
分批或改用
(SQLAlchemy)这类封装好的安全方法
注意 PostgreSQL 对
参数数量有限制(默认 65535),超量时得改用
配合 JOIN
除了参数化,还有哪些必须同步做的底线措施
参数化是核心,但不是全部。比如错误信息泄露表结构、数据库账号权限过大、或未校验输入类型,都会放大攻击后果。
容易踩的坑:认为“我用了 prepare 就万事大吉”,结果调试时开着
,一条报错直接暴露字段名和表名;或者用 root 账号连库,被注入后执行
。
关闭生产环境的详细错误输出,用日志记录而非页面返回;MySQL 设置
防止宽泛类型隐式转换
数据库账号仅授予最小必要权限:
,禁用
、
、
等高危权限
对数字型参数强制类型转换:
再传入参数化查询——这不是替代方案,而是双保险
最麻烦的其实是动态表名、列名、ORDER BY 字段这类无法参数化的场景。它们没法用占位符,只能靠白名单严格校验,比如把允许的排序字段限定为
,其余一概拒绝。这点容易被忽略,但恰恰是真实项目中最常出问题的地方。
escape_stringmysqli_query($conn, "SELECT * FROM users WHERE id = '" . $_GET['id'] . "'")' OR 1=1 --mysqli_prepare()PDO::prepare()mysql_*pg.query('SELECT * FROM users WHERE id = $1', [req.query.id])pg.query(`SELECT * FROM users WHERE id = ${req.query.id}`)sqlite3.execute("SELECT * FROM users WHERE name = ?", (name,))?:nameUser.objects.filter(name='xxx')extra()raw()extra_tablesDB::select(DB::raw("SELECT * FROM {$table}"))$table${xxx}#{xxx}IN (?, ?, ?, ...)?$ids = [1, 5, 9]; $placeholders = str_repeat('?,', count($ids) - 1) . '?'; $stmt = $pdo->prepare("SELECT * FROM posts WHERE id IN ($placeholders)"); $stmt->execute($ids);executemanyin_()INUNNEST(ARRAY[...])display_errors = OnSELECT LOAD_FILE('/etc/passwd')sql_mode = 'STRICT_TRANS_TABLES'SELECT, INSERT, UPDATEFILELOAD DATAEXECUTE$id = (int)$_GET['id'];['created_at', 'title', 'status']