返回博客

一行 $noNeedLogin = ['*'] 让后台 API 全裸奔

某次对一个基于 FastAdmin(ThinkPHP 5)开发的后台管理系统做安全审查,翻出了四个串联在一起的安全问题。单看每个都"不算大",合在一起却能让任何人无需登录、任意枚举全库数据。这篇文章把整条根因链条拆开讲清楚。

现象

审查时发现四个 API 控制器均 extends app\common\controller\Api,理论上受 FastAdmin 内置认证保护。但实测直接用无 token 的 HTTP 请求访问会员列表、手机号搜索、积分明细等敏感接口,全部正常返回数据,毫无阻拦。

根因

第一坑:$noNeedLogin = ['*'] 认证全关

FastAdmin 的 Api 基类在 _initialize() 里读取 $noNeedLogin 属性:若包含当前方法名,则跳过 token 校验。['*'] 是通配,等价于"所有方法都不需要登录"。

这行代码极有可能是开发阶段为了方便本地调试加进去的,上线前忘了删。

// 危险 — 所有方法跳过认证
protected $noNeedLogin = ['*'];
 
// 同样危险 — 权限校验也全关
protected $noNeedRight = ['*'];
坑:一行通配让整个控制器对外公开

$noNeedLogin = ['*'] 不是"宽松",是"完全关闭"。任何人不带 token 都能访问全部接口。这个模式通常在开发环境为了省事加进去,然后随代码一起上线。

第二坑:whereRaw 字符串拼接——SQL 注入

审查发现多处 whereRaw 直接把用户输入拼进 SQL 字符串:

// 危险 — 字符串拼接,用户可注入任意 SQL
$query->whereRaw('points >= ' . floatval($input));
$query->whereRaw("name LIKE '%" . $input . "%'");

即使加了 floatval,形如 LIKE 的字符串场景根本没有类型强制,注入面仍然存在。

第三坑:LIKE 通配符枚举

搜索手机号的接口直接用:

// 危险 — 用户输入 % 匹配全表,输入 _ 枚举任意单字符
$query->where('phone', 'like', '%' . $phone . '%');

攻击者搜索一个 % 就能返回全量手机号;搜索 1____0000 之类的模式可以枚举特定号段。

第四坑:分页参数无边界

// 危险 — pagesize 可被设为 999999,一次捞全表
$pagesize = $this->request->post('pagesize');

结合前面认证全关,攻击者一次请求就能把整张表拉走。

正解

认证配置:删掉 $noNeedLogin = ['*']Api 基类默认要求所有方法登录,只对真正需要公开的方法单独声明:

// OK — 只有 publicEndpoint 不需要登录
protected $noNeedLogin = ['publicEndpoint'];

SQL 注入:全量替换 whereRaw 字符串拼接,改用参数绑定或 ThinkPHP 链式查询:

// 安全 — 参数绑定
$query->whereRaw('points >= ?', [floatval($input)]);
 
// 更安全 — 直接用链式 API(永远安全)
$query->where('points', '>=', floatval($input));

LIKE 通配符:搜索前转义 %_

$phone = str_replace(['%', '_'], ['\\%', '\\_'], $phone);
$query->where('phone', 'like', '%' . $phone . '%');

分页边界:强制约束范围:

$page     = max(1, intval($this->request->post('currpage', 1)));
$pagesize = min(100, max(1, intval($this->request->post('pagesize', 10))));
正解:部署前跑四条 grep 扫一遍
# 1. 认证通配
grep -rn 'noNeedLogin.*\*\|noNeedRight.*\*' application/api/controller/
 
# 2. whereRaw 无参数绑定
grep -rn 'whereRaw' application/api/controller/ | grep -v '?'
 
# 3. LIKE 未转义(有 $ 变量直接拼入)
grep -rn "like.*%.*\$" application/api/controller/
 
# 4. 分页无边界
grep -rn 'pagesize\|page_size' application/api/controller/

四条命令两分钟跑完,把它加进 CI 或上线前检查单。

实测

把上述检查命令打包成一个小脚本,对整个 application/api/controller/ 目录过一遍:两分钟内找出 4 个控制器、7 处 noNeedLogin = ['*']、3 处 whereRaw 拼接、5 处未转义 LIKE——全都在同一批功能里,同一个开发习惯导致的连锁问题。

一句话外卖

FastAdmin 开发阶段为了省事加的 $noNeedLogin = ['*'],是最容易被带上线的一行安全炸弹——把它和 whereRaw 拼接的 grep 命令加进上线检查单,比任何 code review 都管用。