vr-shopxo-plugin/docs/council-eval-securityengine...

206 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Council 评估报告SecurityEngineer — Round 3 最终版
**日期**2026-05-26
**审计范围**支付链路购物车→支付→QR票生成+ Issue #6 + FOR UPDATE SKIP LOCKED + 前端XSS
---
## 一、现状评估
vr-shopxo-plugin 票务链路安全水位:**中高**。核心安全机制均已到位QR签名HMAC-SHA256+8字符截断、短码混淆Feistel网络、悲观锁核销verifyTicket事务+FOR UPDATE、幂等发票order_id+seat_info唯一键、ShopXO原子库存扣减。**无发现 P0 安全漏洞。**
---
## 二、安全问题全面审计
### S-1issueTicket() 并发竞态 — P0 建议不等同P0漏洞
**文件**`TicketService.php:151-154`
```php
$existing = Db::name(BaseService::table('tickets'))
->where('order_id', $order['id'])
->where('seat_info', $spec_name)
->find(); // ← TOCTOU 窗口
```
**实际风险分析**
- ShopXO 的 `onOrderPaid` 回调由内核触发,在支付流水通知层面已有幂等保护
- 只有在 ShopXO 重试回调 + 库存扣减成功但票未生成的极边缘场景下才会并发调用 issueTicket
- 此时 ShopXO 层的原子条件 `UPDATE WHERE inventory >= N` 已经完成了库存扣减,超卖不会发生
**缓解措施**:已有 ShopXO 原子扣减作为主要防线issueTicket 幂等检查作为第二层。**建议加唯一索引** `uk_order_seat(order_id, seat_info)` 作为根本性防护,但不应急迫。
**结论**:⚠️ **可接受,建议修复但可延后**
---
### S-2FOR UPDATE SKIP LOCKED 概念澄清 — P2
**实际情况**
| 场景 | 实现 | 防护有效性 |
|------|------|-----------|
| 并发下单扣库存 | ShopXO `WHERE inventory >= N` + `dec()` 原子UPDATE | ✅ 有效 |
| 并发发票 | issueTicket() 幂等检查(无行锁) | ⚠️ 建议加唯一索引 |
| 并发核销 | verifyTicket() `lock(true)` (FOR UPDATE) | ✅ 有效 |
`FOR UPDATE SKIP LOCKED` 在此代码库中**不是防超卖的关键**。真正防超卖的是 ShopXO 的原子条件 UPDATE不是行锁。verifyTicket 的 FOR UPDATE 已有SKIP LOCKED 是优化而非必须。
**结论**:✅ **可接受,概念澄清完毕**
---
### S-3QR Secret 硬编码 — P1
**文件**`BaseService.php:298-302`
```php
private static function getVrSecret(): string
{
// $secret = env('VR_TICKET_SECRET', ''); // 注释掉了
$secret = '8935b3a3-a7b4-4e3d-8c1f-9b7e2a6f5d4c'; // ← 硬编码 fallback
if (empty($secret)) {
throw new \Exception('请在.env中设置VR_TICKET_SECRET=...');
}
return $secret;
}
```
**注意**`getQrSecret()`用于QR加密是**强制要求**配置 `.env`,未配置则抛异常。而 `getVrSecret()`用于HMAC签名有硬编码 fallback。
**风险**:源码泄露时,攻击者可伪造短码。但 HMAC 签名本身(使用 goods_id 派生 key仍提供了一定隔离。
**缓解**:生产环境确保 `.env` 中配置 `VR_TICKET_SECRET`
**结论**:⚠️ **P1建议确认.env配置可延后至安全专项**
---
### S-4$goodsId 未定义导致 ClearCache 失效 — P3 Bug
**文件**`TicketService.php:126`
```php
SeatMapService::ClearCache(intval($goodsId)); // $goodsId 在 onOrderPaid 中未定义
```
**影响**:座位图缓存在支付后不会被清除,下一位买家可能看到过期已售座位。**不影响票务安全链路**(库存以 ShopXO 数据库为准)。
**正确代码应为**
```php
SeatMapService::ClearCache(intval($og['goods_id']));
```
**结论**:🟢 **P3 Bug不影响安全可延后修复**
---
### S-5前端 XSS$goods['content'] 未转义)— P3
**文件**`ticket_detail.html:75`
```php
<?php echo $goods['content']; ?> // 管理后台富文本直接输出
```
管理员可注入恶意JS但风险范围仅限管理后台高信任用户。观演人表单字段real_name、phone、id_card均已使用 `htmlspecialchars()` 转义,状态良好。
**结论**:🟢 **P3管理面可控可延后**
---
## 三、安全防线评估矩阵(最终版)
| 威胁 | 保护机制 | 强度 | 状态 |
|------|---------|------|------|
| 超卖(并发下单) | ShopXO `dec()` 原子条件UPDATE | 🟢 强 | ✅ 已防护 |
| 超卖(同一座位被多次发票) | issueTicket() 幂等检查 | 🟡 中 | ✅ 建议加唯一索引 |
| 伪造QR票 | HMAC-SHA256签名ticket_code绑定 | 🟢 强 | ✅ 已防护 |
| 伪造短码 | Feistel混淆+goods_id派生key | 🟡 中 | ✅ 已防护 |
| 重复核销 | verifyTicket() FOR UPDATE 悲观锁 | 🟢 强 | ✅ 已防护 |
| QR票过期重放 | 30分钟 exp + iat + code 字段 | 🟢 强 | ✅ 已防护 |
| 环境密钥泄露 | `getVrSecret()` 硬编码 fallback | 🔴 危 | ⚠️ 需确认.env |
| 前端XSS | 富文本XSS管理面可控 | 🟡 中 | ⚠️ 可延后 |
---
## 四、Issue #6 结论
**当前代码中无 P0 安全漏洞。** 所有问题均为 P1-P3不应作为主攻方向阻塞项。
---
## 五、安全维度投票
**议题:下一步主攻方向**
**投票C — 双线并行**
**理由**
1. 安全所有问题均为 P1-P3无 P0 漏洞,**不应阻塞开发主轴**
2. 后端 API 完善seatSpecMap Hook 注册、extension_data 链路)是当前唯一真正的阻塞点
3. 安全加固(.env确认、P1唯一索引可以与前端开发并行推进不互相依赖
4. 最大化团队效率,同时保证安全改进不遗漏
**对其他提案的评估**
- **A后端优先**:合理,但完全阻塞前端会浪费 H5 保底能力和前端团队资源
- **B前端优先**H5 票务页是好的过渡,但不能忽视 uniapp 是主要目标
- **DPhase 4 优先)**Tree API 是差异化功能Phase 3 核心流程尚未完全稳定,不应跳级
---
---
## 六、Round 4 现场核查确认
### 安全行动项现场核实
**S-4 Bug 确认**`TicketService.php:126`
```php
SeatMapService::ClearCache(intval($goodsId)); // $goodsId 未定义
```
✅ 确认存在。正确代码应为 `$og['goods_id']`。此 Bug 不影响安全ShopXO DB 库存为准),但会导致座位图缓存失效迟缓,影响用户体验。
**S-3 QR Secret 硬编码确认**`BaseService.php:302`
```php
$secret = '8935b3a3-a7b4-4e3d-8c1f-9b7e2a6f5d4c'; // 确认存在
```
✅ 确认存在。生产环境需配置 `.env``VR_TICKET_SECRET`
**S-1 幂等检查确认**`TicketService.php:151-154`
```php
$existing = Db::name(BaseService::table('tickets'))
->where('order_id', $order['id'])
->where('seat_info', $spec_name)
->find(); // 无行锁,但有幂等保护
```
✅ 确认存在。ShopXO `onOrderPaid` 由内核回调触发,已在支付流水层有幂等保护。唯一建议是加唯一索引 `uk_order_seat(order_id, seat_info)`,但可延后。
### BackendArchitect P0 重分类背书
BackendArchitect Round 4 修正了 Round 1-3 的 P0 评估:
- `getSoldSeats()` 方法缺失 → **已消除**`SeatMapService::GetSeatMap()` 已含库存)
- `Index.php::soldSeats` Fatal Error → **已消除**Index.php 无此 action
- `plugins_service_goods_data` Hook 未注册 → **降为 P1**UniApp 可用 `/seatmap` 变通)
我**背书 BackendArchitect 的 P0 重分类**。与我的安全评估一致:无 P0 安全漏洞。
---
## 七、安全维度投票Round 4 确认)
**议题:下一步主攻方向**
**投票C双线并行**Round 1/2/3/4 一致,不变)
**理由**
1. 安全所有问题均为 P1-P3**无 P0 安全漏洞,不应阻塞开发主轴**
2. 后端 P0 重分类(无 P0确认安全不阻塞后端 API 完善Hook 注册、seatSpecMap 注入)是当前真正阻塞点
3. 安全加固(.env 确认、P1 唯一索引)可与前端开发并行推进,不互相依赖
4. H5 `ticket_detail.html` 可作为独立过渡方案,立即推进 `loadSoldSeats()` 实现
**补充**BackendArchitect 投票 A后端优先有合理性Hook 注册是最小改动最大收益),但前端 H5 无阻塞可立即推进,两者不冲突。
---
*报告人SecurityEngineer | 2026-05-26 | Round 4 核查完成*