sql注入可绕过简单拼接校验,因单引号闭合、注释符、union select等属sql解析器正常行为;前端过滤和js校验无效,参数化查询须确保驱动将输入当数据而非sql片段处理,表名、排序字段等动态语法位置必须白名单限制。

SQL 注入是怎么绕过简单拼接校验的
只要用户输入进了 query 字符串拼接,就大概率能被绕过。比如用单引号闭合、注释掉后面校验逻辑、或用 UNION SELECT 拖库——这些不是“高级技巧”,而是 SQL 解析器的正常行为。
常见错误现象:' OR '1'='1 成功登录;admin' -- 跳过密码检查;后端日志里出现 mysql_affected_rows() returned -1 却没报错。
- 别信“我过滤了单引号和分号”——
/**/、%00、宽字节(如%df%27)都能绕过 - 前端 JS 校验完全无效,攻击者直接发 POST 请求
- ORM 的
.filter(name__icontains=xxx)看似安全,但如果传入的是 raw string 而非参数化表达式,照样中招
参数化查询在不同语言里怎么写才真正生效
参数化不是加个问号就行,关键看驱动是否把值当“数据”而非“SQL 片段”送进执行器。Python 的 sqlite3 用 ? 或 :name 是安全的,但若用 f"WHERE id = {user_id}",前面全白搭。
使用场景:所有带用户输入的 SELECT、INSERT、UPDATE、DELETE,包括分页偏移、排序字段(需白名单限制)、LIKE 模糊匹配。
- PHP PDO:
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = ?"); $stmt->execute([$status]);—— 必须用execute()传参,不能bindValue()后再拼字符串 - Java JDBC:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM log WHERE level = ?"); ps.setString(1, level);——setString()才触发转义,ps.executeQuery("SELECT ...")是错的 - Node.js pg:
client.query('SELECT * FROM items WHERE id = $1', [id])—— 不能用模板字符串插值,$1必须原样保留
哪些地方看似用了参数化,其实还是有注入风险
表名、列名、ORDER BY 字段、LIMIT 数值——这些语法位置不接受参数占位符,硬塞 ? 会直接报错:ERROR: syntax error at or near ""。这时候如果靠字符串拼接,就等于开门揖盗。
该系统采用多层模式开发,这个网站主要展示女装的经营,更易于网站的扩展和后期的维护,同时也根据常用的SQL注入手段做出相应的防御以提高网站的安全性,本网站实现了购物车,产品订单管理,产品展示,等等,后台实现了动态权限的管理,客户管理,订单管理以及商品管理等等,前台页面设计精致,后台便于操作等。实现了无限子类的添加,实现了动态权限的管理,支持一下一个人做的辛苦
性能影响不大,但逻辑漏洞极难审计。线上出问题往往就卡在这类“边缘位置”。
- 动态表名:必须走白名单校验,比如
if table_name not in ['users', 'orders']: raise ValueError - 排序字段:只允许
['created_at', 'score'],且映射到固定 SQL 片段,不拼接原始输入 - LIKE 模糊匹配:
WHERE name LIKE '%' || ? || '%'(PostgreSQL)或CONCAT('%', ?, '%')(MySQL),不能写成"'%"+input+"%'" - MySQL 的
SET NAMES utf8mb4这类语句,绝不能把编码名从 URL 取来拼进去
预编译语句失效的两个隐蔽原因
即使写了 prepare + execute,也可能因环境配置或驱动行为退化为字符串拼接。最典型的是 MySQL 的 sql_mode 和连接参数。
兼容性影响明显:某些旧版 PHP + mysqlnd 驱动在未开启 MYSQLI_OPT_PREPEND_STMT 时,会静默降级;Go 的 database/sql 默认启用预编译,但 mysql 驱动若设了 interpolateParams=true,就等于自废武功。
- MySQL 连接字符串里含
useServerPrepStmts=false(Java)或client_flag=CLIENT_PROTOCOL_41(Python PyMySQL)会导致预编译被忽略 - PostgreSQL 的
pgbouncer在 transaction pooling 模式下不支持预备语句,PREPARE会报prepared statement "xyz" does not exist - 调试方法:开启数据库通用日志(
general_log=ON),看实际执行的 SQL 是否含用户输入原文
真正难的不是写对一行 prepare,而是确保整个调用链路——从 HTTP 参数解析、中间件透传、ORM 封装层、驱动配置,到数据库服务端模式——没有一处偷偷把参数又拼回了 SQL 字符串里。









