[安全-P0] P0-1/3/4已修复,P0-2价格验证待讨论需求 #6

Closed
opened 2026-04-15 01:46:44 +00:00 by sileya-ai · 2 comments

🔴 P0-1:onOrderPaid 无幂等保护

文件: service/TicketService.php:23-68
来源: Council 三方共识

ShopXO 支付回调通过 HTTP 请求触发。若支付渠道重试或 ShopXO 多实例部署,同一订单可能多次触发 onOrderPaid,每次生成独立有效票(UUID 不同),两张票都可入场核销。

修复: 在 issueTicket 前增加幂等检查:

$existing = \Db::name(BaseService::table("tickets"))
    ->where("order_id", $order["id"])
    ->column("spec_base_id", "id");
if (!empty($existing)) {
    return true; // 已发放,跳过
}

🔴 P0-2:购票参数前端计算无服务端验签(价格可被篡改)

文件: view/goods/ticket_detail.html submit() 函数
来源: Council 三方共识

购票参数(goods_id、spec_base_id、stock、extension_data)全由前端 JavaScript 计算后拼接 URL。服务端不对 extension_data 验签。

攻击路径: DevTools 控制台执行 vrTicketApp.selectedSeats.forEach(s => s.price = 0.01) → 以 0.01 元完成支付。

修复: 改为 POST 请求,服务端验价重算,不信任前端参数。


🔴 P0-3:XSS 漏洞(simple_desc 和 content 均使用 |raw)

文件: view/goods/ticket_detail.html:139、163
来源: Council 三方共识

<div class="vr-event-subtitle">{\.simple_desc|default=\'\'|raw}</div>
<div class="goods-detail-content">{\.content|raw}</div>

{|raw} 完全绕过 ThinkPHP 自动 HTML 转义。管理员注入 <img src=x onerror=...> 可窃取所有访问商品页用户的 session cookie。

修复: 移除 |raw,改用 htmlspecialchars 或白名单过滤。


🔴 P0-4:QR 加密密钥硬编码默认回退

文件: service/BaseService.php:98-107
来源: Council 三方共识

return config("shopxo.app_key", "shopxo_default_secret_change_me");

若 VR_TICKET_QR_SECRET 未配置,系统以已知不安全密钥运行,所有 QR 密文可被离线破解。

修复: 环境变量缺失时主动抛出异常,不静默回退。

## 🔴 P0-1:onOrderPaid 无幂等保护 **文件:** service/TicketService.php:23-68 **来源:** Council 三方共识 ShopXO 支付回调通过 HTTP 请求触发。若支付渠道重试或 ShopXO 多实例部署,同一订单可能多次触发 onOrderPaid,每次生成独立有效票(UUID 不同),两张票都可入场核销。 **修复:** 在 issueTicket 前增加幂等检查: ```php $existing = \Db::name(BaseService::table("tickets")) ->where("order_id", $order["id"]) ->column("spec_base_id", "id"); if (!empty($existing)) { return true; // 已发放,跳过 } ``` --- ## 🔴 P0-2:购票参数前端计算无服务端验签(价格可被篡改) **文件:** view/goods/ticket_detail.html submit() 函数 **来源:** Council 三方共识 购票参数(goods_id、spec_base_id、stock、extension_data)全由前端 JavaScript 计算后拼接 URL。服务端不对 extension_data 验签。 **攻击路径:** DevTools 控制台执行 `vrTicketApp.selectedSeats.forEach(s => s.price = 0.01)` → 以 0.01 元完成支付。 **修复:** 改为 POST 请求,服务端验价重算,不信任前端参数。 --- ## 🔴 P0-3:XSS 漏洞(simple_desc 和 content 均使用 |raw) **文件:** view/goods/ticket_detail.html:139、163 **来源:** Council 三方共识 ```html <div class="vr-event-subtitle">{\.simple_desc|default=\'\'|raw}</div> <div class="goods-detail-content">{\.content|raw}</div> ``` {|raw} 完全绕过 ThinkPHP 自动 HTML 转义。管理员注入 `<img src=x onerror=...>` 可窃取所有访问商品页用户的 session cookie。 **修复:** 移除 |raw,改用 htmlspecialchars 或白名单过滤。 --- ## 🔴 P0-4:QR 加密密钥硬编码默认回退 **文件:** service/BaseService.php:98-107 **来源:** Council 三方共识 ```php return config("shopxo.app_key", "shopxo_default_secret_change_me"); ``` 若 VR_TICKET_QR_SECRET 未配置,系统以已知不安全密钥运行,所有 QR 密文可被离线破解。 **修复:** 环境变量缺失时主动抛出异常,不静默回退。
sileya-ai self-assigned this 2026-04-15 01:46:44 +00:00
Poster
Owner

西莉雅审核(2026-04-15)

全部 P0 问题仍在。

**西莉雅审核(2026-04-15)** 全部 P0 问题仍在。
Poster
Owner

P0-1 / P0-3 / P0-4 已修复(commit 098bcfe7)

P0-1 幂等保护issueTicket() 增加幂等检查,同一 (order_id, spec_base_id) 已有票则直接返回现有票ID。

P0-3 XSSticket_detail.htmlsimple_desccontent|raw 已移除。

P0-4 QR 密钥getQrSecret() 在环境变量为空时主动抛出异常,不再静默回退。


P0-2 价格验证 — 需求待讨论

ShopXO BuyService 本身已在下单时从数据库读取规格单价写入 order_goods.goods_price,绕过前端直接下单时 ShopXO 也会用真实价格计算。P0-2 的修复涉及 onOrderPaid 业务逻辑的较大改动,在没有确认需求细节的情况下暂停实现。

等待大头明确:

  1. extension_data.seats[] 价格字段应验证什么?
  2. 不同价格区(A区 + B区 混选)是否在支持范围内?

确认后再实现。

## P0-1 / P0-3 / P0-4 已修复(commit 098bcfe7) **P0-1 幂等保护** — `issueTicket()` 增加幂等检查,同一 `(order_id, spec_base_id)` 已有票则直接返回现有票ID。 **P0-3 XSS** — `ticket_detail.html` 中 `simple_desc` 和 `content` 的 `|raw` 已移除。 **P0-4 QR 密钥** — `getQrSecret()` 在环境变量为空时主动抛出异常,不再静默回退。 --- ## P0-2 价格验证 — 需求待讨论 ShopXO `BuyService` 本身已在下单时从数据库读取规格单价写入 `order_goods.goods_price`,绕过前端直接下单时 ShopXO 也会用真实价格计算。P0-2 的修复涉及 `onOrderPaid` 业务逻辑的较大改动,在没有确认需求细节的情况下暂停实现。 等待大头明确: 1. `extension_data.seats[]` 价格字段应验证什么? 2. 不同价格区(A区 + B区 混选)是否在支持范围内? 确认后再实现。
sileya-ai changed title from [安全-P0] 5个严重安全问题待修复 to [安全-P0] P0-1/3/4已修复,P0-2价格验证待讨论需求 2026-04-15 09:03:36 +00:00
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: sileya-ai/vr-shopxo-plugin#6
There is no content yet.