ThinkPHP 5.1 的 limit 分页本身安全,因 offset 和 size 经强制整型转换并使用预处理绑定;真正风险在于未校验的动态排序字段、分组字段及手动拼接的子查询SQL。

ThinkPHP 5.1 的 limit 分页本身不直接受 SQL 注入影响,因为 paginate() 方法默认生成的 LIMIT offset, size 中,offset 和 size 参数由框架自动转换为整型(强制类型转换),不会拼进 SQL 字符串。但**真正风险不在 limit 数值本身,而在于分页所依赖的查询条件、排序字段、以及开发者为“优化分页”手动拼接的 SQL 片段**。
limit 参数本身是安全的,但 offset 来源必须可信
TP5.1 对 paginate() 的 listRows(每页条数)和当前页码(用于算 offset)默认做整型强制转换,例如:
-
input('page', 1, 'intval')→ 页码转为 int -
input('limit', 15, 'intval')→ 每页数转为 int - 最终计算出的
offset = (page - 1) * limit也是整数,直接用于LIMIT ?, ?绑定,无注入风险
高危场景:动态 order/group 字段未校验
分页常配合排序(order())使用,而 order() 不支持参数绑定字段名——这是注入重灾区:
- 危险:
order(input('sort') . ' ' . input('order'))→ 用户可传id; DROP TABLE user-- - 正确做法:白名单校验
示例:
$allowSort = ['id', 'create_time', 'title'];$allowOrder = ['asc', 'desc'];$sort = in_array(input('sort'), $allowSort) ? input('sort') : 'id';$order = in_array(input('order'), $allowOrder) ? input('order') : 'desc';$list = Db::name('article')->order($sort . ' ' . $order)->paginate(15);更隐蔽的风险:子查询分页中手动拼接 ID 或条件
为解决深分页性能问题,开发者常改用子查询方式(如 SELECT * FROM t JOIN (SELECT id FROM t ORDER BY id LIMIT 100000,20) tmp USING(id))。此时若子查询中的 WHERE 条件或 ORDER BY 字段来自用户输入,且未校验,就会绕过所有预处理防护:
- 危险:
Db::query("SELECT * FROM article t JOIN (SELECT id FROM article WHERE status = " . input('s') . " ORDER BY " . input('by') . " LIMIT ?, ?)", [$offset, $size]) - 安全写法:所有动态部分走白名单 + 数值参数严格绑定
推荐实践:封装游标分页替代 offset 分页
避免大 offset,也规避其带来的校验负担。游标分页依赖上一页最后一条记录的主键值(如 id > 1000),该值来自数据库已有数据,无需用户可控输入参与计算:
- 前端传
last_id=1000(上一页末尾 id),后端只做整型判断 - 查询:
where('id > ?', [input('last_id')])->limit(15)->order('id ASC')→ 全部走参数绑定 - 字段名(
id)、操作符(>)、数值(last_id)全部受控,无拼接空间
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











