vr-shopxo-plugin/reviews/SecurityEngineer-on-GhostSp...

5.3 KiB
Raw Blame History

SecurityEngineer — 幽灵 spec 安全审计汇总报告

文件路径基于 /Users/bigemon/WorkSpace/vr-shopxo-plugin/ 审计时间2026-04-20 参与者SecurityEngineer安全审计、BackendArchitect根因分析、FrontendDev前端分析


执行摘要

对「场馆删除后编辑商品出现规格重复错误」问题进行了三方安全审计。核心根因已定位,P1 安全缺陷已识别。


审计范围

文件 用途
shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php 商品保存钩子vr_goods_config 处理
shopxo/app/plugins/vr_ticket/service/SeatSkuService.php 批量 SKU 生成,模板不存在时的 fallback
shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html 顾客端座位选购页面
shopxo/app/plugins/vr_ticket/admin/Admin.php 场馆硬删除逻辑
shopxo/app/admin/hook/AdminGoodsSave.php ShopXO 商品保存钩子入口

根因分析SecurityEngineer

根因 1P1无效 template_id 配置块未被过滤

文件AdminGoodsSaveHandle.php:148-173

vr_goods_config 中存在 template_id 指向已删除场馆的配置块时:

  1. save_thing_end 从 DB 加载 config第 61-66 行)
  2. 遍历 configs 尝试重建 template_snapshot(第 77 行)
  3. 若模板不存在,continue 跳过 snapshot 重建(第 88-90 行)
  4. 整个 config 块(含旧的 template_snapshot)被写回 DB(第 148-150 行)
  5. BatchGenerate 被调用时,若 template_id 仍为正整数但模板不存在,返回 code: -2 阻止保存

关键缺陷:若 config 块的 template_id 被前端置为 0(模板选单为空),则 templateId > 0falseBatchGenerate 整个循环体被跳过,无任何校验直接写回。

根因 2P1幽灵 spec 持续污染 vr_goods_config

脏 config 块(含已删除模板的 template_snapshot)被写回 DB 后:

  • 下次编辑商品时,vr_goods_config 仍含无效配置
  • GetGoodsViewData 尝试加载模板(失败后将 template_id 置 null
  • 但若 save_thing_end 在模板验证前先执行写回,无效配置再次被保存
  • 循环往复,幽灵 spec 永远无法被清理

根因 3P2前端无 vr_goods_config_base64 输入保护

AdminGoodsSaveHandle.php:29-35 接收前端传入的 base64 编码配置:

  • 无 schema 校验(不验证 template_id 是否为正整数)
  • 无类型校验(不验证是否为数组)
  • 管理员可直接 POST 恶意 JSON 注入 vr_goods_config

前端分析(参考 ticket_detail.html

硬删除场景下的 fallback

SeatSkuService::GetGoodsViewData 在模板不存在时:

  • vr_seat_template 返回 null
  • goods_config.template_idnull
  • goods_config.template_snapshotnull
  • goods_spec_data 返回空数组

前端 ticket_detail.html 读取 seatMap = []specBaseIdMap = [],座位图不渲染。设计正确

安全风险

  1. loadSoldSeats() 未实现ticket_detail.html:375-383TODO 注释状态,无法标记已售座位。顾客可购买已售座位(需支付验证拦截)。
  2. submit 依赖 specBaseIdMap(第 417 行):空时降级 sessionSpecId。理论上可操控座位数据选择任意座位。
  3. 无直接 XSS:后端输出均有编码,seatMapspecBaseIdMap 来自 DB 合规数据。

严重性分级

等级 数量 描述
P1 2 无效 template_id 静默保存;幽灵 spec 无法清理
P2 3 Admin API 无 schema 校验;残留 snapshot 信息泄露specBaseIdMap 端侧无验证
0 无直接 XSS

修复方案

P1-1/P1-2拒绝无效 template_id必须

AdminGoodsSaveHandle.php:158-173 需在调用 BatchGenerate 前验证:

foreach ($configs as $config) {
    $templateId = intval($config['template_id'] ?? 0);
    if ($templateId <= 0) {
        return ['code' => -401, 'msg' => '票务配置中的 template_id 无效或已被删除'];
    }
    $exists = Db::name('vr_seat_templates')->where('id', $templateId)->find();
    if (empty($exists)) {
        return ['code' => -401, 'msg' => 'template_id [' . $templateId . '] 指向的场馆已不存在'];
    }
    $res = SeatSkuService::BatchGenerate(...);
    if ($res['code'] !== 0) {
        return $res;
    }
}

P2-1过滤无效 config 块(必须)

在写回 DB 之前过滤掉 template_id <= 0 的配置块:

$validConfigs = array_filter($configs, function($c) {
    return intval($c['template_id'] ?? 0) > 0;
});
if (empty($validConfigs)) {
    return ['code' => -401, 'msg' => '票务商品必须包含至少一个有效场馆配置'];
}
Db::name('Goods')->where('id', $goodsId)->update([
    'vr_goods_config' => json_encode($validConfigs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);

结论

幽灵 spec 的根因是后端未拒绝脏数据,而非前端注入。save_thing_end 在模板验证失败时静默保留了无效的 config 块,导致 vr_goods_config 中的幽灵 spec 永远无法被清理。修复方向明确:任何 template_id 为空或指向不存在场馆的配置块,都必须被过滤或拒绝保存,并返回 code: -401 告知用户重新选择场馆。