后台接口鉴权我加了 token 校验,却栽在了一个没校验 HTTP 方法的开关上

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 54 浏览 7 回复

上周给后台的敏感操作接口补安全漏洞,复盘的时候发现一个特别隐蔽的坑,跟各位站长分享一下。

事情是这样的。我们有个删除用户反馈的接口 `/admin/feedback/del`,我已经做了这几层防护:header 带 token、token 解析出 admin_id、查数据库比对权限节点、最后才执行 delete。本地测试扫了一遍,觉得稳了。

结果安全群友拿工具一跑,直接一个 `` 嵌到测试页面里,反馈数据没了。我当场愣住——token 呢?权限校验呢?全绕过了。

根因特别蠢:这个接口只校验了 `$_POST` 参数里的 token,没限制请求方法。浏览器对 ``、``、`` 这些标签发起的是 GET 请求,而我的接口 `route::any()` 注册的,GET 进来一样走删除逻辑。token 校验那行代码在 `if ($this->request->isPost())` 里面,GET 请求直接跳过去执行了。

说白了,这不是鉴权被破解,是鉴权根本没被触发。

补完这个洞之后,我把后台接口的安全 checklist 重新捋了一遍,这几条现在写进项目模板了:

1. 路由层面先锁死方法

能用 `route::post()` 绝不用 `route::any()`,需要文件上传的单独拆 `route::post()` + `route::get()`。ThinkPHP 里 `route::rule()` 第三个参数默认是 `*`,新手很容易忽略。我现在强制团队写成 `Route::post('feedback/del', 'del')->middleware('admin_auth')`,方法不对直接 405,进不到控制器。

2. 中间件里做"无差别"校验,别指望控制器自觉

之前我把 token 校验写在基类控制器的 `initialize()`,但有的同事写新模块直接继承了 `think\Controller`,绕过去了。现在改成中间件,所有 `/admin/*` 路由统一挂 `AdminAuth` 中间件,校验不通过直接抛 `HttpException(401)`,控制器层根本碰不到。

3. CSRF token 和 JWT 分场景,别混着用

我们后台用 JWT 做状态保持,但表单提交额外加了 CSRF token。之前图省事想砍掉一层,实测不行——JWT 在 cookie 里自动带,CSRF 就是钻这个空子。现在保留双 token:JWT 验身份,CSRF token 验请求来源,两者校验逻辑完全隔离。

4. SQL 注入防护别只依赖参数绑定

有个搜索接口用了 `whereRaw("FIND_IN_SET({$ids}, category_id)")`,`$ids` 虽然前端是数字,但接口没强制类型。传个 `1) OR 1=1#` 进去直接裸奔。现在规定:所有 `whereRaw`、`orderRaw`、`fieldRaw` 必须走白名单或强制类型转换,代码 review 时重点标红。

5. 权限校验粒度要落到"数据行"

之前只校验了"有没有删除反馈的权限",没校验"这条反馈属不属于他该看的"。现在敏感操作加了一层数据范围校验,从 `auth_rule` 扩展到 `auth_group_data_scope`,查之前先 `where('admin_id', 'in', $this->dataScope)`,防止水平越权。

最后说个感受:安全这玩意,防的不是"高手破解",是防"我自己忘了"。接口一多,总有个把角落里的 `any()`、某个偷懒的 `whereRaw`,或者某个没继承对基类的控制器。现在我的做法是,新接口必须填安全自查表,CI 里加了一层路由扫描脚本,自动抓 `any` 注册和 raw 方法调用,提交就报警。

各位站长有没有类似"以为稳了其实漏了"的经历?欢迎交流,互相补补盲区。

评论7
回复 · 7
BrightPath
BrightPath 新手 · #7 ·
同求,期待更新
安然77
安然77 新手 · #6 ·
同求,期待更新
Alex
Alex 新手 · #5 ·
同求,期待更新
🐱汽水🎐
🐱汽水🎐 新手 · #4 ·
同求,期待更新
知夏45
知夏45 新手 · #3 ·
同求,期待更新
🌙旅人🧡
🌙旅人🧡 新手 · #2 ·
学到了,顶一下
chenshao
chenshao 新手 · #1 ·
kk
微信客服 微信客服