后台那个"登录成功"的弹窗,差点让我背上一口泄露用户数据的锅

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

上个月帮朋友看一个用了两年的CMS后台,登录接口返回的JSON里居然带着完整的`password_hash`和`role_id`。前端倒是聪明,只渲染了头像和昵称,但F12一开,所有超管字段裸奔。这哥们还纳闷:"我按钮都做了权限隐藏啊,怎么数据层也防不住?"

这事让我重新捋了遍后台安全的几个真·雷区,不是框架文档里那种"请做好鉴权"的废话,是实打实踩过坑才长的心眼。

一、别信前端的"已隐藏",数据出口要二次过滤

很多ORM查询图省事直接`select *`,ThinkPHP的`hidden`字段只在序列化时生效,如果中间加了`toArray()`再手动拼装,隐藏规则就断了。我现在养成个习惯:任何返回给前端的数组,必须过一遍白名单,哪怕多写十行字段映射。超管看全量、编辑看部分、普通用户看摘要,三层`field()`各查各的,宁可多两次查询也不共用一套数据集。

更阴的是关联查询里的漏网之鱼。`with(['user'])`时如果user模型没设隐藏,密码字段跟着文章列表一起吐出去,日志都不一定打全。

二、CSRF Token别只防表单,AJAX接口同样要验

我见过最离谱的做法:登录后的所有POST都带Token,但DELETE和PUT用的RESTful路由,前端库默认没加,后端也没单独校验。结果一个构造好的``直接绕过,因为框架只读了`X-Requested-With`头判断是不是AJAX,而form提交可以伪造这个头——至少在某些旧版中间件配置里可以。

现在的做法是给Token加绑定:不只是校验值对不对,还要校验这个Token是不是当前会话签发的。TP6里可以用`token`验证器的`bind`参数,或者自己存一份`session_id`到Token payload里,解密时比对。多这一步,Token泄露后的利用窗口小很多。

三、SQL注入的"新皮肤":orderBy和field里的变量

预编译防住了WHERE条件,但`order($sortField)`和`field($columns)`里的字符串是直接拼接的。有回接了个需求,让用户自选列表排序字段,前端传`sort=create_time`,后端直接`->order($sort)`。我多了个心眼加了白名单校验,后来审计发现前任代码里有个`->field(Input::get('fields'))`,能直接读出任意字段,包括密码。

现在这类入口统一走映射表:`sort`传`time`映射到`create_time`,`fields`传`base`映射到预设字段组。用户输入永远不进SQL结构,只当字典的key用。

四、权限校验的"最后一公里":动作级鉴权比菜单级更重要

很多人做RBAC只做到"这个用户能不能进后台",但同一个编辑后台里,"修改自己的文章"和"修改任意文章"是两套权限。我现在的校验链是:中间件拦未登录 → 控制器构造函数拦无菜单权限 → 具体方法里再拦无动作权限。三层都过,才执行业务。

最坑的是那种"通用接口",比如`/api/upload`全站共用,后台用、前台用户也用、甚至未登录的访客场景也用。这种接口必须在入口处根据上下文分流,不能一套逻辑吃遍天。我吃过亏:上传接口没区分场景,结果前台用户传图拿到了后台存储路径,顺藤摸瓜直捅OSS的私有bucket——虽然没泄露文件,但目录结构暴露了。

五、日志要记"谁试图做什么",不是只记"谁成功了"

鉴权失败、参数越界、Token校验不通过,这些我现在全写进独立日志表,带IP、UA、请求摘要。不是为了抓内鬼,是出事时能复盘攻击路径。有回发现某个IP连续几百次尝试不同uid访问`/admin/user/detail`,虽然都被拦了,但说明对方已经拿到了有效的session cookie,只是在撞权限边界。及时清掉那批session,比事后找泄露点快得多。

安全这东西,框架给的是底裤,不是铠甲。底裤穿了,但冷不丁还是会被树枝划到腿。多一层自己的校验,多一分睡踏实的可能。

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