diff --git a/plan.md b/plan.md index 3f2c9ca..71247af 100644 --- a/plan.md +++ b/plan.md @@ -39,12 +39,12 @@ ## Task Checklist -- [ ] R1: 评审插件架构 (EventListener.php / plugin.json) -- [ ] R2: 评审票务核心 (TicketService.php / BaseService.php) -- [ ] R3: 评审前端页面 (ticket_detail.html) -- [ ] R4: 评审数据库 Schema (001_vr_tables.sql) -- [ ] R5: 安全性综合审计(注入/XSS/重放/QR伪造) -- [ ] R6: 汇总评审报告 (reviews/code-review-FrontendDev.md) +- [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) --- @@ -52,9 +52,20 @@ | Phase | 内容 | 状态 | |---|---|---| -| **Draft** | 各维度代码阅读 + 问题识别 | ⏳ Pending | -| **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无长度限制 | --- @@ -62,7 +73,7 @@ | Task | Owner | Status | |---|---|---| -| R1-R6: 完整评审 | council/FrontendDev | `[Claimed: council/FrontendDev]` | +| R1-R6: 完整评审 | council/FrontendDev | `[Done: 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 += '