CRMEB-BZ:门店列表 /api/store_list SQL 注入 — 漏洞说明与修改明细
============================================================
路径均相对站点 crmeb/ 根目录(与修复包内路径一致)。
----------------------------------------
一、修改了哪些文件(总览)
----------------------------------------
序号 | 修改文件 | 改动类型 | 修改位置(方法) | 改了什么
1 | app/dao/system/store/SystemStoreDao.php | 修改方法 | distance() | 经纬度先 (float) 再拼进 SQL
2 | app/api/controller/v1/PublicController.php | 修改方法 | store_list() | 入参先经纬度校验再查库
3 | app/api/controller/v1/PublicController.php | 新增方法 | checkCoordinate() | 数值格式与 ±90/±180 区间校验
4 | app/api/controller/v1/PublicController.php | 文件头部 use | use crmeb\exceptions\ApiException; | 供校验失败抛业务异常
5 | app/api/ApiExceptionHandle.php | 修改方法 | render() | 生产环境不再回显 SQL 等未预期异常原文
6 | app/api/ApiExceptionHandle.php | 文件头部 use | 增加 HttpException、HttpResponseException;调试判断改用 $this->app->isDebug() | 配合 render 分支
修复包内含 3 个文件:
crmeb/app/api/ApiExceptionHandle.php
crmeb/app/api/controller/v1/PublicController.php
crmeb/app/dao/system/store/SystemStoreDao.php
----------------------------------------
二、漏洞简述
----------------------------------------
GET /api/store_list?latitude=...&longitude=... 中,旧版把经纬度字符串直接拼进距离计算 SQL。攻击者可用 extractvalue / updatexml 做报错注入,外带 eb_system_config 里的云 AK 等敏感配置。
----------------------------------------
三、按文件 / 按位置的详细修改
----------------------------------------
文件 1:app/dao/system/store/SystemStoreDao.php
位置:distance() 方法(注入点本体)
项目 | 说明
5 月约行号 | 约第 40 行起
修复后约行号 | 约第 45 行起
谁调用 | 同文件 getStoreList()(约第 65 行):有经纬度时 $query->field(['*', $this->distance(...)])
改前(危险写法):
public function distance(string $latitude, string $longitude)
{
return "(round(6367000 * 2 * asin(... ({$latitude} ... {$longitude} ...)))) AS distance";
}
用户输入原样进入 field() 原始 SQL 片段,框架不做参数绑定。
改后:
public function distance(string $latitude, string $longitude)
{
$latitude = (float)$latitude;
$longitude = (float)$longitude;
return "(round(6367000 * 2 * asin(... ({$latitude} ... {$longitude} ...)))) AS distance";
}
方法注释中说明:返回值会进 SQL,必须先转 float。
改了什么地方(一句话):在 return 拼接字符串之前,增加两行强制 (float) 转换。
----------------------------------------
文件 2:app/api/controller/v1/PublicController.php
位置 1:文件头部 use 声明
* 新增:use crmeb\exceptions\ApiException;
* 原因:checkCoordinate() 校验失败时抛出业务异常。
位置 2:store_list() 方法
项目 | 说明
5 月约行号 | 约第 390 行起
修复后约行号 | 约第 394 行起
调用路由 | GET /api/store_list(app/api/route/v1.php)
改前:
list($latitude, $longitude) = $request->getMore([
['latitude', ''],
['longitude', ''],
], true);
$data['list'] = $services->getStoreList(..., $latitude, $longitude);
$request 取值后直接传入服务层,无校验。
改后(在 getMore 与 getStoreList 之间插入):
$latitude = $this->checkCoordinate($latitude, 90, '纬度');
$longitude = $this->checkCoordinate($longitude, 180, '经度');
$data['list'] = $services->getStoreList(..., $latitude, $longitude);
改了什么地方(一句话):store_list() 里获取参数之后、调用 getStoreList() 之前,增加两行坐标校验。
说明:若本地另有注释掉 tengxun_map_key 的返回,属于额外改动,与 SQL 注入修复点不同;以你实际下发的 PublicController.php 为准。
位置 3:新增私有方法 checkCoordinate()
项目 | 说明
修复后约行号 | 约第 420 行起(紧接在 store_list() 之后)
5 月代码 | 无此方法
新增逻辑:
条件 | 行为
空字符串 | 返回 ''(不按距离排序)
非数值 | 抛 ApiException('{name}格式不正确')
超出 ±$max | 抛 ApiException('{name}超出有效范围')
合法 | 返回 (string)(float)$value
$max:纬度传 90,经度传 180。
----------------------------------------
文件 3:app/api/ApiExceptionHandle.php
位置 1:文件头部 use
改前 | 改后
use think\facade\Env;(在 render 里读 debug) | 增加 use think\exception\HttpException;
增加 use think\exception\HttpResponseException;
调试状态改为 $this->app->isDebug()
位置 2:render() 方法
项目 | 说明
5 月约行号 | 约第 79 行起
修复后约行号 | 约第 80 行起
改前(方法末尾):
对非业务异常(含数据库 DbException)直接:
return app('json')->fail($message, $massageData);
$message 可含 XPATH syntax error: '~密钥片段',造成报错外带。
改后(在原有 Api/Auth/Validate 分支之后增加):
1. HttpException / HttpResponseException → parent::render(...)(保持框架行为)。
2. 其它未预期异常:
* 非 debug:return app('json')->fail('系统繁忙,请稍后再试');
* debug:仍返回详细 $message(仅开发环境)。
改了什么地方(一句话):render() 末尾,给「数据库等未预期异常」增加生产环境脱敏分支,不再把 SQL 原文返回客户端。
----------------------------------------
四、修改位置速查(给实施同学)
----------------------------------------
SystemStoreDao.php
└─ distance()
└─ return 之前是否有 (float)$latitude / (float)$longitude ← 必须有
PublicController.php
├─ use ApiException ← 必须有
├─ store_list()
│ └─ getMore 之后是否调用 checkCoordinate ← 必须有
└─ checkCoordinate() ← 必须有(新增)
ApiExceptionHandle.php
└─ render()
└─ 非 debug 时是否返回「系统繁忙,请稍后再试」 ← 必须有
----------------------------------------
五、三层防护对应关系
----------------------------------------
层级 | 文件 | 位置 | 作用
入口 | PublicController.php | store_list + checkCoordinate | 拒绝非数字 / 超范围参数
拼 SQL | SystemStoreDao.php | distance | 强制 float,注入字符串变无害数字
回显 | ApiExceptionHandle.php | render | 生产不回显 SQL 报错内容
----------------------------------------
六、如何覆盖
----------------------------------------
1. 备份原三个文件。
2. 解压 zip,按路径覆盖到站点 crmeb/。
3. 确认 .env 中 APP_DEBUG=false。
4. 清缓存 / 重启 PHP-FPM。
5. 若日志已出现外带证据,立即轮换云 AK / 短信等密钥。
无数据库脚本。

