后台鉴权那道"暗门":我因为把用户ID存在Cookie里,被测试同事一键穿越成了超管

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

上周测试组的小兄弟给我发了个截图,说他没登录就能进后台,还能点删除。我第一反应是"你肯定缓存没清",结果他当场录屏——从访客页面F12改了个Cookie值,刷新后直接变成UID为1的管理员。

我当时后背就凉了。不是凉在漏洞本身,是凉在我居然在三个月前的某次"临时调试"里,把 `uid` 和 `role_id` 明文塞进了Cookie,后面忘了拆。更蠢的是,我的中间件鉴权逻辑长这样:

```php
$uid = Cookie::get('admin_uid');
$user = User::find($uid);
if ($user && $user->status == 1) {
    // 放行
}
```

看到没?没验签、没过期、没跟Session对账,甚至连个httponly都没设。测试同事随便编了个`1`就进来了。这已经不是鉴权了,这是"猜数字开门"。

连夜重构的时候,我把Cookie这整条路封死了,改成`Session + 短期Token`双绑。但过程中又踩了几个新坑,记录一下:

一、Token存在Redis里,但忘了绑设备指纹

第一版修复我搞了个JWT似的Token存Redis,key是`token:{$user_id}`。结果同事换台电脑登录,直接把上一台的Token顶掉了——这倒不算漏洞,但用户体验很怪。更麻烦的是,如果用户开了两个浏览器,后登录的那个会让前一个"被踢下线"。

后来改成`token:{$user_id}:{$device_hash}`,每个设备独立。但device_hash怎么算?我试了UA+IP,结果公司出口IP是同一个,同事们互相顶。最后妥协成`UA前段+屏幕分辨率+时区`杂糅,不算严谨,但至少同办公室不会互撞了。

二、CSRF防护开了,但把API接口也套进去了

ThinkPHP的表单令牌默认全局开启,我偷懒没做路由分组。结果前端Vue调的后台API全报`token数据无效`,因为axios没自动带那个`_token_`字段。

正确的做法应该是:后台管理路由组开CSRF,纯API路由(尤其是给前端异步调用的)走签名或JWT校验,别混用。我现在的规则是——凡是返回HTML的POST,必须验CSRF;返回JSON的,验Header里的`X-Request-Token`。

三、SQL注入的"温柔陷阱":whereRaw的便利代价

有个搜索功能需要动态字段,我图省事用了`whereRaw`,类似这样:

```php
$whereRaw = "name like '%{$keyword}%'";
User::whereRaw($whereRaw)->select();
```

测试同事输了个`%'; DROP TABLE admin_log; --`,虽然MySQLi预处理挡住了执行,但日志里实实在在出现了这条SQL。我后来换成`where('name', 'like', "%{$keyword}%")`,强制走参数绑定。动态字段那种实在绕不开的,用白名单枚举,绝不信用户传的字段名。

四、最隐蔽的一刀:权限缓存没同步

鉴权走Redis缓存了角色权限树,但我在后台改完权限后,只清了角色的缓存,忘了清当前在线用户的Session绑定。结果有个编辑被提成了管理员,刷新菜单确实变了,但点进去还是报403——因为Session里存的老角色ID没更新。

现在的方案是:改权限时发一条Channel消息,所有在线后台Session收到后自动刷新`role_id`和权限树。简单粗暴,但有效。

这次事件给我最大的教训是:鉴权不能靠"看起来没问题"。明文Cookie、裸whereRaw、缓存不同步,单独看都是小疏忽,串起来就是裸奔。建议各位站长找个周末,专门F12改改自己的Cookie试试,说不定有惊喜。

你们有没有类似的"一键变超管"经历?欢迎分享,让我知道我不是最蠢的那个。

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