后台鉴权我写了五层,却被一个 `action` 拼写错误整破防了

站长杂谈 22 浏览 0 回复 返回上级

上周给后台补权限,想着这回总算稳了:路由中间件拦一道、Controller 基类再校验一次角色、每个方法开头手动 `checkAuth()`、API 层加了 Token 过期验证、最后数据库操作前还套了层数据范围过滤。五层护城河,自我感觉良好。

结果测试同事甩过来一个截图:普通编辑账号,成功调到了 `adminLog/deleteAll`。我当场愣住,这接口明明没在权限节点里开放啊。

查了两小时日志,最后盯着路由注册文件发呆——`AuthController` 里写的是 `deleteAll`,权限节点表里录的是 `deleteall`,而前端菜单配的是 `delete-all`。三个地方三种写法,我的中间件正则匹配刚好漏掉了这个变种。五层校验,每一层都以为"下一层会拦住",结果层层穿透,跟纸糊的一样。

这事儿给我敲了个狠的:权限校验的脆弱性往往不在"有没有",而在"对没对上"。现在我把所有权限标识全收进一个常量类,路由、节点表、前端菜单统一从这个类取,编译期就报错,运行期不再靠人眼对。

再说说 CSRF 的坑。之前图省事,后台接口全改成 JSON + Header Token,以为不用管 CSRF 了。直到某天发现站内有个富文本编辑器,用户能提交任意 HTML。攻击者搭了个钓鱼页,诱导已登录的管理员点一下,表单自动提交到后台改配置接口——虽然带了 Token,但那是存在 Cookie 里的,同源请求自动带上,服务端验 Token 通过,配置被改掉。

后来才搞明白:Token 放 Cookie 还不行,得让攻击者拿不到、带不上。现在我的做法是双 Token:Cookie 里一个 HttpOnly 的刷新令牌,接口请求头里再带一个从独立接口获取的短期行动令牌,两者绑定校验。富文本那块的写入接口额外加了 Referer 白名单和自定义 Header 校验,三道锁扣死。

SQL 注入这块我反倒觉得现在比以前更隐蔽了。框架的查询构造器用惯了,手写 Raw 的地方越来越少,但 `orderByRaw`、`groupByRaw`、`whereRaw` 这些"后门"还在。上个月接了个需求,后台列表支持自定义排序字段,前端传字段名,后端直接 `orderByRaw(request('sort_field') . ' ' . request('sort_order'))`。当时觉得字段名白名单校验过了,方向只能是 asc/desc,能出啥事?

直到有人把 `sort_field` 改成 `(select sleep(5))`,虽然没读出数据,但请求直接卡五秒。要是配合堆叠查询或者写文件,这口子就大了。现在所有动态字段都过一层映射表,前端传"创建时间",后端翻译成 `created_at`,绝不直接拼接。`sort_order` 也只认枚举值,多余字符直接抛异常。

还有个容易忽略的:批量操作的权限粒度。后台经常有"选中十条删除",接口收到 ID 数组。我之前的校验是先看用户有没有 `batchDelete` 权限,有就放行。结果用户 A 只能看部门一的数据,他传了 `[1,2,3,4,5]`,其中 3 和 5 是部门二的,一起给删了。

现在批量操作必须拆成单条校验循环,或者 SQL 层面加 `WHERE dept_id IN (...)` 的数据范围限制。性能是差了点,但权限这事,宁慢勿漏。实在数据量大的场景,先按数据范围过滤出用户可见的 ID 集合,再和传入的 ID 取交集,没交集的直接拒掉。

最后说个反直觉的:日志也要防注入。有回排查问题,看操作日志发现某个管理员账号做了条异常记录,内容里带着 `\x00` 和换行符,把日志格式搅乱了,后面解析全错位。攻击者没打进数据库,但把日志分析系统搞瘫了,照样影响溯源。现在所有进日志的用户输入,先过一遍不可见字符过滤,长度截断,敏感符号转义。

安全这东西,从来不是某个单点做扎实就高枕无忧。我现在每写完一块权限逻辑,都会强迫自己换个角色想:如果我是只拿到一个普通 Cookie 的攻击者,从哪条缝能挤进来?往往想着想着,就发现某层校验的"理所当然"其实经不起较真。

评论0
回复 · 0
还没有回复
微信客服 微信客服