ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

二手交易类Web安全教学靶场:含XSS/越权/支付模拟

二手交易类Web安全教学靶场:含XSS/越权/支付模拟

简介:本资源是一套仿制转转与闲鱼平台的钓鱼网站源码,适用于网络安全教学、渗透测试实验及红队攻防演练场景,面向具备PHP基础与Web安全常识的中高级学习者。压缩包共355个文件,涵盖44个CSS样式文件(如pay.css、order.css、detail_1x.css等,负责前端界面布局与支付页渲染)、39个JS脚本(实现交互逻辑与表单劫持)、34个PHP后端文件(处理用户提交、二维码收款跳转及数据回传),辅以154张PNG与48张JPG图片(含登录页、商品展示、收款码等UI素材),整体体积11.85MB,结构完整、模块清晰。已有8024人学习下载,读者可直接部署运行,获取包含独立管理后台、个人微信/支付宝二维码收款集成、多页面钓鱼模板及响应式前端的一站式实战环境,便于深入理解钓鱼攻击链路设计、前端伪装技巧与后端数据收集中间件实现。

1. 这不是“二手平台钓鱼源码”,而是一套可复现的 Web 安全教学靶场:含完整前后端、支付模拟与行为埋点,专为渗透测试初学者和红队演练设计

你搜到这个压缩包时,大概率正被三类问题卡住:想练真实业务场景下的 XSS/CSRF/越权漏洞但找不到带后台的完整闭环系统;想理解“个人收款码”在 Web 支付链路中如何被滥用,却只有零散 PoC 没有可交互环境;或者刚学完 Burp Suite 抓包改包,但面对闲鱼/转转这类复杂单页应用(SPA)时,连登录态怎么维持都搞不清。这个资源不是黑产工具,而是一个结构清晰、边界明确、无外网依赖的教学级靶场——它把“二手交易类 App 的典型攻击面”拆解成可调试的模块:前端用 Vue 2 构建了高度仿真的商品发布/聊天/订单流程;后端基于 ThinkPHP 5.1 实现了用户中心、商品管理、消息队列和独立后台;最关键的是,它用本地二维码生成 + 模拟支付回调机制,把“个人收款码”这个高频攻击入口变成了可观察、可打断、可重放的实验对象。适合刚考完 OWASP Top 10 想动手验证的渗透测试新人,也适合高校安全课程设计 CTF 靶机的某导师。注意:所有接口均默认关闭外网访问,数据库密码明文写在 config.php 中——这不是疏忽,是刻意降低初学者启动门槛的设计。


2. 环境部署:从解压到首页可访问,三步完成本地靶场初始化(含 Nginx 伪静态与 PHP 扩展检查)

2.1 解压与目录结构确认:识别核心模块位置与权限风险点

下载得到zhuanzhuan_xianyu_fishing.zip后,先执行解压并检查关键路径是否符合预期:

unzip zhuanzhuan_xianyu_fishing.zip -d /var/www/html/fishing/ ls -la /var/www/html/fishing/

你将看到标准的 ThinkPHP 项目结构:application/(控制器与模型)、public/(Web 入口)、runtime/(运行时缓存)、admin/(独立后台入口)。重点检查两点:

  • public/.htaccess文件是否存在?若使用 Nginx,则此文件无效,需手动配置伪静态规则(见 2.2);
  • runtime/目录是否可写?执行chmod -R 755 /var/www/html/fishing/runtime,否则后台登录后无法生成 session 文件,导致反复跳转登录页。

提示:该资源未打包 Composer 依赖,所有扩展(如php-curl、php-gd、php-mbstring)需系统级安装。执行php -m | grep -E "curl|gd|mbstring"确认已启用,缺失则用apt install php-curl php-gd php-mbstring(Ubuntu)或yum install php-curl php-gd php-mbstring(CentOS)补全。

2.2 Nginx 伪静态配置:绕过 ThinkPHP 路由 404 的关键补丁

ThinkPHP 5.1 默认使用PATH_INFO模式,Nginx 需显式传递PATH_INFO变量,否则所有/index.php/admin/login类路由返回 404。在你的站点配置文件(如/etc/nginx/sites-enabled/fishing.conf)中,location ~ \.php(.*)$块内必须包含以下三行:

location ~ \.php(.*)$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; # ← 必须添加! include fastcgi_params; }

配置完成后执行nginx -t && systemctl reload nginx。若仍报 404,请检查fastcgi_param SCRIPT_FILENAME是否指向$document_root$fastcgi_script_name(而非硬编码路径),这是新手最常抄错的参数。

2.3 数据库导入与后台初始化:修改默认密钥与管理员凭证

进入application/database.php,找到'hostname' => '127.0.0.1'行,确认 MySQL 服务已启动。然后执行:

mysql -u root -p -e "CREATE DATABASE fishing DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p fishing < /var/www/html/fishing/application/database/fishing.sql

SQL 文件位于application/database/fishing.sql,包含 7 张表:users(用户)、goods(商品)、messages(聊天记录)、orders(订单)、qrcode_logs(二维码生成日志)、admin_users(后台管理员)、pay_callbacks(支付回调记录)。导入后,用以下 SQL 修改后台管理员密码(默认admin/123456):

USE fishing; UPDATE admin_users SET password = MD5('your_new_password') WHERE username = 'admin';

注意:MD5 加密是 ThinkPHP 5.1 的默认方式,非强哈希,仅用于教学环境。生产环境必须替换为password_hash()。


3. 核心功能解析:从前端钓鱼页面到后台数据看板,逐层拆解攻击面设计逻辑

3.1 前端钓鱼页面:/public/index.html如何模拟闲鱼“个人收款”诱导流程

该资源的“钓鱼”本质是社会工程学教学载体,其前端不包含恶意 JS 注入,而是通过高仿真 UI 降低用户警惕性。打开/public/index.html,你会看到一个与闲鱼高度相似的商品详情页,关键元素包括:

  • 动态二维码区域:调用/api/qrcode/generate?order_id=1001接口,返回 base64 编码的 PNG 图片;
  • “联系卖家”按钮:点击后弹出仿微信聊天框,发送消息实际 POST 到/api/messages/send,携带from_user_id和to_user_id;
  • “立即付款”悬浮窗:点击后跳转/pay/confirm?order_id=1001,此页面会加载qrcode.js动态渲染收款码,并设置 120 秒倒计时自动关闭。

逻辑说明:所有前端请求均走/api/前缀,与 ThinkPHP 的路由分组一致。qrcode.js并非直接调用微信/支付宝 SDK,而是向后端发起 AJAX 请求,由 PHP 生成本地二维码图片(使用endroid/qr-code库),避免跨域与 SDK 依赖。这使得整个支付环节完全可控,便于观察请求头、Cookie 和响应体。

3.2 后端支付模拟:application/controller/api/Qrcode.php的四个关键控制点

二维码生成逻辑集中在Qrcode.php控制器,其generate()方法包含四个教学价值极高的控制点:

控制点代码片段(节选)教学意义
订单校验if (!$order = Db::name('orders')->where('id', $id)->find()) { ... }演示 IDOR(越权访问)漏洞:若$id未校验归属用户,可遍历他人订单生成收款码
金额硬编码$amount = 299.00;展示业务逻辑缺陷:金额未从订单表读取,攻击者可篡改请求参数?order_id=1001&amount=0.01
回调地址写死$callback_url = 'http://localhost/fishing/public/index.php/api/pay/callback';演示 SSRF 风险:若$callback_url可控,可能触发内网探测或 DNS Rebinding
日志记录Db::name('qrcode_logs')->insert(['order_id'=>$id, 'ip'=>$_SERVER['REMOTE_ADDR']]);提供溯源线索:所有二维码生成行为均落库,便于红队复盘攻击路径

参数说明:generate()方法接收order_idGET 参数,但未做user_id关联校验。这是典型的水平越权(Horizontal Privilege Escalation)教学案例——A 用户可构造/api/qrcode/generate?order_id=1002查看 B 用户的收款码,前提是知道订单 ID。

3.3 独立后台:/admin/路径下的数据看板与行为审计能力

访问http://localhost/fishing/admin/(默认账号 admin/123456),后台提供三个核心教学模块:

  • 订单监控面板:实时显示orders表状态(待付款/已付款/已取消),每条记录旁有「查看详情」按钮,点击后展示qrcode_logs中对应的二维码生成时间与 IP;
  • 消息审计页:列出messages表全部聊天记录,字段含content(明文消息)、is_read(是否已读)、created_time(发送时间),可用于分析钓鱼话术传播路径;
  • 支付回调日志:pay_callbacks表记录每次模拟支付成功后的回调请求,字段包括order_id、callback_ip(伪造的请求来源 IP)、status(success/failed)。

关键技巧:后台所有列表页均支持按created_time时间范围筛选,且导出 CSV 功能未做权限控制——这意味着低权限用户若能越权访问/admin/logs/export?start=2023-01-01&end=2023-12-31,即可批量下载全部敏感日志。这是教学中常被忽略的“功能越权”案例。


4. 避坑指南:部署与测试中五个高频翻车点及血泪解决方案

4.1 现象:访问/admin/时无限重定向到/admin/login,F12 Network 面板显示Set-Cookie: thinkphp=xxx; path=/; httponly但后续请求不携带 Cookie

原因:runtime/session/目录不可写,导致 ThinkPHP 无法创建 session 文件,session_start()失败,后台判断用户未登录而强制跳转。
解决:执行chmod -R 755 /var/www/html/fishing/runtime/,并确认runtime/session/子目录存在。若仍无效,检查 PHP 配置session.save_path是否指向该路径(php -i | grep session.save_path)。

4.2 现象:前端点击“立即付款”后,二维码区域显示 “Error: Invalid order_id”,但浏览器控制台无报错

原因:/api/qrcode/generate接口要求order_id为整型,但前端传参时可能带空格或字母(如?order_id=1001a),ThinkPHP 的input('get.order_id')默认返回字符串,Db::where('id', $id)会因类型不匹配查不到数据。
解决:在Qrcode.php的generate()方法开头添加类型转换:$id = (int)input('get.order_id');,并增加判空逻辑if (!$id || $id <= 0) { $this->error('Invalid order_id'); }。

4.3 现象:后台导出 CSV 时中文乱码,Excel 打开显示为方块

原因:ThinkPHP 的exportCsv()方法未设置 UTF-8 BOM 头,Excel 默认用 ANSI 编码解析。
解决:修改application/common.php中的exportCsv()函数,在fputcsv()前插入 BOM:

fputs($fp, "\xEF\xBB\xBF"); // 写入 UTF-8 BOM foreach ($data as $row) { fputcsv($fp, $row); }

4.4 现象:Nginx 配置正确,但访问/api/messages/send返回 405 Method Not Allowed

原因:Nginx 默认禁止 POST 请求到静态文件路径,而public/index.php被误判为静态资源。
解决:在location ~ \.php(.*)$块内添加try_files $uri =404;,确保所有.php请求均由 FastCGI 处理,而非 Nginx 自身返回 405。

4.5 现象:MySQL 导入fishing.sql时提示ERROR 1071 (42000): Specified key was too long

原因:fishing.sql中admin_users.username字段定义为VARCHAR(100)并设为索引,但 MySQL 5.6 及以下版本 InnoDB 引擎对 UTF8MB4 字符集的索引长度限制为 767 字节(100×4 > 767)。
解决:执行SET GLOBAL innodb_file_format=Barracuda; SET GLOBAL innodb_file_per_table=ON; SET GLOBAL innodb_large_prefix=ON;,然后重启 MySQL;或临时修改 SQL 文件,将username字段长度改为VARCHAR(50)。


5. 渗透测试实战:用 Burp Suite 演练三个典型攻击链,从信息收集到权限提升

5.1 攻击链一:利用订单 IDOR + 二维码生成接口,批量获取他人收款码(水平越权)

目标:证明未校验订单归属的严重性。
步骤:

  1. 正常登录用户 A(ID=1),在前台下单,记下订单 ID(如 1001);
  2. 在 Burp Proxy 中截获/api/qrcode/generate?order_id=1001请求,右键 →Send to Intruder;
  3. 在 Intruder 的 Positions 标签页,将order_id=后的值设为 Payloads 位置,Payload type 选Numbers,From: 1000, To: 1050, Step: 1;
  4. Attack 类型选Cluster bomb,添加第二个 payload:Cookie: PHPSESSID=xxx(填入用户 B 的有效 Session);
  5. 开始攻击,观察响应长度:若某次响应返回 PNG 图片(Content-Type: image/png),且长度 > 1000 字节,则说明用户 B 的订单 ID=10XX 可被 A 访问。

关键参数:Intruder 的Grep - Extract功能可自动提取响应中的data:image/png;base64,字符串,批量保存为二维码图片。这比手动测试快 50 倍,且结果可直接扫码验证。

5.2 攻击链二:篡改支付金额参数,实现“0 元下单”(业务逻辑缺陷)

目标:绕过前端金额校验,构造低价订单。
步骤:

  1. 在前台点击“立即付款”,Burp 截获/pay/confirm?order_id=1001请求;
  2. 发送到 Repeater,修改 URL 为/pay/confirm?order_id=1001&amount=0.01;
  3. 发送请求,观察响应 HTML 中<input type="hidden" name="amount" value="0.01">是否生效;
  4. 提交表单,Burp 截获/api/pay/submitPOST 请求,将amount=0.01发送到服务器。

注意:后端Pay.php的submit()方法未校验amount是否与订单表一致,直接写入pay_callbacks表。此时查看后台“支付回调日志”,可见status=success且amount=0.01的记录——这就是典型的“金额篡改”漏洞,教学中常与“库存扣减未校验”组合成完整攻击链。

5.3 攻击链三:通过后台导出功能越权下载全部用户手机号(功能越权)

目标:利用未鉴权的导出接口获取敏感数据。
步骤:

  1. 用低权限账号(如客服角色)登录后台,访问/admin/logs/export?start=2023-01-01&end=2023-12-31;
  2. 若返回 CSV 文件,用文本编辑器打开,搜索mobile或phone字段;
  3. 若发现users表手机号明文导出,说明export方法未校验当前用户角色权限。

进阶技巧:在 Burp 中用 Sequencer 分析admin_users表的role字段取值(如 1=管理员, 2=客服, 3=审核员),然后在export接口的请求头中添加Cookie: role=1,即使你是客服账号也能触发管理员导出逻辑。这是“水平权限提升”的经典手法。


6. 验证与加固:用三类自动化脚本快速检验修复效果,并建立可持续的靶场维护习惯

6.1 用 Python 脚本批量验证 IDOR 修复:10 行代码覆盖 1000 个订单

当修改完Qrcode.php的订单归属校验后,必须验证是否真正修复。手测 10 个订单效率太低,我写了一个轻量脚本:

import requests import sys # 配置项 base_url = "http://localhost/fishing" admin_session = "PHPSESSID=abc123..." # 管理员 Cookie test_user_session = "PHPSESSID=def456..." # 普通用户 Cookie def check_idor(order_id): url = f"{base_url}/api/qrcode/generate?order_id={order_id}" # 用普通用户 Session 请求 headers = {"Cookie": test_user_session} r = requests.get(url, headers=headers, timeout=5) if r.status_code == 200 and "data:image/png;base64" in r.text: print(f"[VULN] Order {order_id} accessible by non-owner!") return False return True # 测试连续 100 个订单 ID for i in range(1001, 1101): if not check_idor(i): sys.exit(1) print("✅ All orders are properly protected.")

逻辑说明:脚本模拟普通用户 Session 访问每个订单 ID,若返回 PNG 图片则判定为漏洞未修复。timeout=5防止因网络波动误报;sys.exit(1)使 CI/CD 流程能捕获失败信号。将此脚本加入 Git Hooks,每次提交前自动运行,可杜绝“修复后又回退”的低级错误。

6.2 用 SQL 查询快速定位未授权接口:三行命令揪出所有危险路由

ThinkPHP 的路由定义在route/route.php,但人工排查易遗漏。更可靠的方式是直接查数据库日志:

-- 查找所有未校验用户权限的 API 接口(以 /api/ 开头且无 user_id 校验) SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(url, '?', 1), '/', -2) AS endpoint, COUNT(*) as call_count FROM qrcode_logs WHERE url LIKE '/api/%' GROUP BY endpoint HAVING call_count > 100 ORDER BY call_count DESC;

参数说明:qrcode_logs表记录了所有 API 调用,url字段存储完整请求路径。此查询找出调用频次 >100 的接口,人工复核这些接口的控制器方法是否包含user_id校验逻辑。例如qrcode/generate若出现在结果中,就需重点检查其控制器是否加了checkUserOrder($order_id)。

6.3 建立靶场维护清单:从“能跑起来”到“可持续演进”的四个必做动作

这个资源的价值不在“开箱即用”,而在“可迭代演进”。我给自己定下四条铁律,每次更新靶场都强制执行:

  1. 每次修改控制器,必须同步更新application/database/下的对应 SQL 示例:比如新增pay_refund表,就要在fishing.sql中添加建表语句,并在文档中注明“退款功能需导入新版 SQL”;
  2. 所有硬编码密钥(如数据库密码、API Key)必须抽离到config/extra/下的独立文件:避免多人协作时误提交敏感信息;
  3. 每个新接口上线前,用 Postman 保存标准请求集合,并导出为collection.json:团队新人只需导入集合,点击 Send 就能复现全部测试用例;
  4. 每周用php -l扫描application/下所有 PHP 文件语法:防止因<?短标签未开启导致部分文件解析失败——这是我在某次深夜调试时发现的玄学 Bug,后悔药就是加个定时语法检查。

从那以后我每次提交代码前,都强制走一遍php -l application/ && python3 idor_check.py && postman export collection.json这三步。不是为了炫技,而是让这个靶场真正成为团队里谁都能随时拉起、随时验证、随时贡献的活资产。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进