diff --git a/plan.md b/plan.md index c14e421..071c915 100644 --- a/plan.md +++ b/plan.md @@ -1,19 +1,57 @@ +<<<<<<< HEAD # Council Plan — vr-shopxo-plugin 安全审议 > Round 1 — 2026-04-15 > Branch: council/SecurityEngineer → main > 状态:**Draft Phase 完成,进入 Review** +======= +# Council Plan — vr-shopxo-plugin 代码审议 + +> Round 1 — 2026-04-15 +> Branch: council/FrontendDev → main +> 状态:**Draft Phase** +>>>>>>> council/FrontendDev --- ## Task Summary +<<<<<<< HEAD 对 vr-shopxo-plugin 票务插件进行完整代码安全审议,输出独立评审报告(≥500字),列出所有发现的问题(严重/中等/轻微/建议),给出具体修复建议。**仅评论不改代码,变更提交本地 worktree。** +======= +对 vr-shopxo-plugin 插件进行全面的代码审议,覆盖插件架构、票务核心、前端页面、数据库 Schema、安全性 5 个维度。**仅评论不改代码**,输出独立评审报告到 `reviews/code-review-FrontendDev.md`。 + +--- + +## Review Scope + +### 1. 插件架构 +- `app/plugins/vr_ticket/EventListener.php` +- `app/plugins/vr_ticket/plugin.json` +- 生命周期钩子实现、数据库迁移策略、菜单/权限注册 + +### 2. 票务核心 +- `app/plugins/vr_ticket/service/TicketService.php` +- `app/plugins/vr_ticket/service/BaseService.php` +- `onOrderPaid()` 并发问题、`verifyTicket()` 核销漏洞、AES QR 加密强度 + +### 3. 前端票务详情页 +- `app/plugins/vr_ticket/view/goods/ticket_detail.html` +- HTML/CSS/JS 质量、座位图渲染逻辑、观演人表单安全性 + +### 4. 数据库 Schema +- `app/plugins/vr_ticket/database/migrations/001_vr_tables.sql` +- 表结构规范、索引合理性、外键关系 + +### 5. 安全性审计 +- SQL 注入、XSS、支付回调重放攻击、QR 票防伪造 +>>>>>>> council/FrontendDev --- ## Task Checklist +<<<<<<< HEAD - [x] 1. 插件架构审计(EventListener.php / plugin.json) - [x] 2. 票务核心审计(TicketService.php / BaseService.php) - [x] 3. 前端票务详情页审计(ticket_detail.html) @@ -38,6 +76,14 @@ | L-02 | 🟢 轻微 | 数据完整性 | AES-CBC 无认证加密 | | L-03 | 🟢 轻微 | 业务逻辑 | `loadSoldSeats` 未实现,存在超卖风险 | | I-01~04 | 💡 建议 | 架构/业务 | 升级迁移、退款钩子、购买上限、表单校验缺失 | +======= +- [x] R1: 评审插件架构 (EventListener.php / plugin.json) +- [x] R2: 评审票务核心 (TicketService.php / BaseService.php) +- [x] R3: 评审前端页面 (ticket_detail.html) +- [x] R4: 评审数据库 Schema (001_vr_tables.sql) +- [x] R5: 安全性综合审计(注入/XSS/重放/QR伪造) +- [x] R6: 汇总评审报告 (reviews/code-review-FrontendDev.md) +>>>>>>> council/FrontendDev --- @@ -45,9 +91,26 @@ | Phase | 内容 | 状态 | |---|---|---| +<<<<<<< HEAD | **Draft** | 各模块代码审计 + 报告撰写 | ✅ Done | | **Review** | 评审其他成员报告 | ⏳ Pending | | **Finalize** | 合并到 main,投票 | ⏳ Pending | +======= +| **Draft** | 各维度代码阅读 + 问题识别 | ✅ 完成 | +| **Review** | 输出完整评审报告 | ✅ 完成 | +| **Finalize** | 提交报告到 main | ⏳ 待合并 | + +--- + +## 问题发现汇总 + +| 严重度 | 数量 | 典型问题 | +|--------|------|---------| +| 🔴 严重 | 2 | 购票参数前端计算无验签 / `$goods.content\|raw` XSS | +| 🟡 中等 | 4 | loadSoldSeats 未实现 / CSS 无响应式 / 座位图渲染边界 / JSON输出XSS | +| 🟢 轻微 | 3 | 已选座位 UI 无状态管理 / 观演人表单无校验 / 座位映射数据泄露 | +| 💡 建议 | 4 | 座位字符集ASCII限制 / 座位数无上限 / spec_base_id缺索引 / 地图JSON无长度限制 | +>>>>>>> council/FrontendDev --- @@ -55,6 +118,7 @@ | Task | Owner | Status | |---|---|---| +<<<<<<< HEAD | 插件架构审计 | council/SecurityEngineer | `[Done]` | | 票务核心审计 | council/SecurityEngineer | `[Done]` | | 前端票务页审计 | council/SecurityEngineer | `[Done]` | @@ -74,3 +138,10 @@ --- **[CONSENSUS: NO]** — Draft 完成,等待其他成员评审 +======= +| R1-R6: 完整评审 | council/FrontendDev | `[Done: council/FrontendDev]` | + +--- + +**[CONSENSUS: NO]** — Round 1 规划完成,待执行审议 +>>>>>>> council/FrontendDev diff --git a/reviews/code-review-FrontendDev.md b/reviews/code-review-FrontendDev.md new file mode 100644 index 0000000..691445b --- /dev/null +++ b/reviews/code-review-FrontendDev.md @@ -0,0 +1,479 @@ +# vr-shopxo-plugin 前端代码评审报告 + +> 评审人:FrontendDev +> 日期:2026-04-15 +> 视角:HTML/CSS/JS 质量 / 座位图渲染逻辑 / 响应式设计 / 用户体验 / 观演人表单安全 +> 交叉参考:已合并 SecurityEngineer 和 BackendArchitect 报告,两者发现高度一致,以下从前端视角补充独立发现 + +--- + +## 一、执行摘要 + +vr-shopxo-plugin 的票务详情页(ticket_detail.html)承担了座位选择、场次切换、观演人信息收集等核心交互。作为用户购票流程的唯一入口,其代码质量直接影响用户体验和系统安全性。 + +经过全面评审,发现**2 个严重前端问题、4 个中等问题、5 项改进建议**。最关键的是**购票参数前端计算无服务端验签**,可导致价格篡改攻击;座位图渲染存在未处理的边界情况,CSS 缺乏响应式适配,移动端体验较差。 + +--- + +## 二、票务详情页(ticket_detail.html)评审 + +### 2.1 🔴 严重 — 购票参数前端计算,价格可被篡改 + +**位置:** 第 384-422 行 `submit()` 函数 + +```javascript +var checkoutUrl = this.requestUrl + '?s=index/buy/index' + + '&goods_params=' + encodeURIComponent(goodsParams); +location.href = checkoutUrl; +``` + +**问题分析:** + +整个购票参数(goods_id、spec_base_id、stock、extension_data)由前端 JavaScript 计算后拼接 URL 跳转至 ShopXO 结算页。服务端**不重新计算价格**,完全信任客户端数据。 + +攻击者可通过以下步骤以 0.01 元购买任意座位: + +1. 打开浏览器开发者工具 +2. 在控制台执行: +```javascript +// 修改座位价格为 0.01 +vrTicketApp.selectedSeats.forEach(s => s.price = 0.01); +vrTicketApp.submit(); +``` + +3. 服务端收到 `goods_params` 中的 `stock` 和 `extension_data`,直接使用,不验价 + +**影响:** +- 价格篡改漏洞(已由 BackendArchitect 标记,本报告从 JS 层面量化攻击路径) +- 前端座位数量无服务端校验,可超购 +- `extension_data` 中的 `seat_info` 可伪造(客户端直接写入 JSON) + +**修复建议:** +```javascript +// 方案一:改为 POST 请求,服务端验价 +$.post(this.requestUrl + '?s=plugins/vr_ticket/index/create_ticket_order', { + goods_id: this.goodsId, + spec_base_id: this.sessionSpecId, + seats: JSON.stringify(this.selectedSeats), + attendees: JSON.stringify(attendees) +}, function(res) { + if (res.code == 0) { + location.href = res.data.checkout_url; + } +}); + +// 方案二:添加 HMAC 签名 +var payload = JSON.stringify({ + goods_id: this.goodsId, + seats: this.selectedSeats, + timestamp: Date.now() +}); +var sig = CryptoJS.HmacSHA256(payload, clientSecret); +location.href = checkoutUrl + '&sig=' + sig; +``` + +### 2.2 🟡 中等 — 座位图渲染缺乏边界情况处理 + +**位置:** 第 255-282 行 `renderSeatMap()` + +**问题一:座位图数据空值未处理** + +```javascript +map.map.forEach(function(rowStr, rowIndex) { + var chars = rowStr.split(''); + chars.forEach(function(char, colIndex) { + if (char === '_' || char === '-') { + // 空白座位处理 + } else { + var seatInfo = seats[char] || {}; // ⚠️ seats 字典可能为空 + var price = seatInfo.price || 0; // 价格为 0 时无座可买 + // ... + } + }); +}); +``` + +**场景:** 后端 `seat_map` JSON 中 `seats` 字段缺失或为空,则所有字符都映射到空对象 `{}`,价格为 0。用户在 UI 上看到座位,但点击后价格显示 ¥0,提交时服务端可能拒绝或接受零价订单。 + +**问题二:座位类型图例颜色可能不匹配** + +```javascript +sections.forEach(function(sec) { + var color = sec.color || '#409eff'; + legendHtml += '
'+sec.name+'
'; +}); +``` + +图例中的 `sec.color` 直接作为 CSS 背景色,未做颜色格式校验(如 `rgb()`、`hsl()`、十六进制混用)。若数据库中存储了非法 CSS 值,可能破坏布局。 + +**问题三:座位 ID 直接使用字符映射,不安全** + +```javascript +'data-seat-id="'+char+'" ' +``` + +`char` 是座位图字符(如 `A`、`B`),直接作为 `seat-id` 属性值。如果 `char` 包含引号或特殊字符(实际上地图定义中不会出现,但作为防御性编程应转义),可能破坏 HTML 属性边界。 + +**修复建议:** +```javascript +// 1. 座位数据为空时给出明确提示 +if (!map.seats || Object.keys(map.seats).length === 0) { + document.getElementById('seatRows').innerHTML = '
座位图配置错误,请联系管理员
'; + return; +} + +// 2. 价格为零时提示用户 +if (price === 0) { + // 标记为"待定价"座位,禁用点击 + rowsHtml += '
'; +} else { + // 正常渲染 +} + +// 3. seat-id 转义 +var safeSeatId = String(char).replace(/"/g, '"'); +``` + +### 2.3 🟡 中等 — CSS 缺少响应式设计,移动端体验差 + +**位置:** 第 4-118 行 `