后台鉴权我写了七层,还是被一个 `?id=1` 把数据全拉走了

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

上周帮朋友看一个用了两年的后台系统,登录有验证码、Session 有有效期、操作日志也记了,看着挺像回事。结果我在地址栏顺手改成 ?id=2,直接看到另一家客户的数据。再改成 ?id=3,又一条。整套鉴权体系,在水平越权面前跟纸糊的一样。

这事让我重新捋了遍后台安全的几个真坑,不是"有没有做",是"做没做对位置"。

一、登录成功不等于鉴权完成

很多人把"能进后台"当成终点,其实这只是起点。我现在的习惯是拆三层:

  • 认证:你是谁(登录、Session、Token)
  • 授权:你能进哪些菜单(RBAC 角色权限)
  • 鉴权:这条数据你能不能碰(数据级权限)

前面说的越权事故,就是只做了前两层,第三层完全空白。现在我的 Controller 里必写一行 checkDataOwner($id),去库里验这条记录的 user_id / tenant_id 跟当前登录人是否匹配。宁可多查一次库,不能漏过去。

二、CSRF 别只依赖中间件

TP 的 form_token 我早年用过,后来前后端分离改 JWT,以为不用管了。直到发现某个 POST 接口没验 Referer,被人用 <form> 跨站提交把管理员邮箱改了。

现在的做法:敏感操作(改密码、删数据、转账相关)强制双校验——JWT 里的用户身份 + 操作令牌(存在 Redis,一次性或短时有效)。不是每个接口都上,但涉及状态变更的必须上。成本不高,被扫的时候能挡掉九成无脑攻击。

三、SQL 注入的"新皮肤"

以为用了参数绑定就高枕无忧?我见过两种翻车:

一种是 order by {$sort}$sort 从前端来,直接拼接,参数绑定救不了。现在我的排序白名单写死成数组映射,['time' => 'create_time', 'hot' => 'view_count'],传别的直接抛异常。

另一种是搜索框的 field like '%{$keyword}%',参数绑定防注入,但 %_ 没转义,被人输入 %_% 直接把全表拖出来。现在搜索前过一遍 str_replace(['%', '_'], ['\%', '\_'], $keyword),再配合长度截断,超过 50 字符直接拒掉。

四、接口权限的"幽灵入口"

有次重构把某个方法从 public 改成 protected,但路由里还注册着,结果变成无需登录就能访问。现在我的检查清单:

  • 路由文件定期扫一遍,看有没有挂着 Route::get('/xxx') 但没进中间件组的
  • Controller 方法如果不是对外接口,一律 private 或移到 Service
  • 调试接口(比如 /test/xxx)上线前必须死,我加了个全局中间件,匹配 test* 路径且非本地 IP 直接 403

五、日志要记"谁想干什么",不是只记"谁成功了"

鉴权失败也要留痕。我现在的日志会记:IP、UA、目标接口、携带参数、失败原因(密码错 / 无权限 / Token 过期)。上周就从日志里发现一组固定 IP 在尝试遍历 /admin/user/edit?id=1~9999,及时封了。

安全这东西,没有"做完"的一天。每次觉得自己考虑周全了,去翻翻 Nginx 访问日志,总有些意想不到的请求路径在试探。保持 paranoid(偏执)不是坏事,尤其是个人站长,出一次事可能直接弃坑。

你们后台有没有遇到过"以为安全了其实漏了"的情况?欢迎交流,我这边还有几个更丢人的案例,等你们先讲。

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