帮我把全站代码,再检查 一下有没有安全漏洞?然后输出解决方案给我。
03:01
思考过程
好的,我来对全站代码进行全面安全审计。这是一个复杂任务,我将并行启动多个搜索代理检查不同维度的安全漏洞。
思考过程
全站安全审计报告 & 解决方案
共审计 5 大维度,发现 54 个安全问题(去重后)。其中:
- P0 致命级(立即修复):4 类,共 8 项
- P1 高危:10 项
- P2 中危:21 项
- P3 低危:15 项
亮点:项目在 SQL 注入(PDO 真实预处理)、命令注入(无危险函数)、代码注入(无 eval/assert)、密码哈希(bcrypt)、CSRF 双重提交 Cookie、登录暴力破解防护等方面做得优秀。
P0 致命级问题(必须 24 小时内修复)
P0-1:敏感文件泄露服务器密码(最高危)
问题文件:
- haoping.sql 第 4 行 — 明文包含宝塔密码、Debian root 密码、MySQL 密码、服务器 IP
- api/db_config -zxw.php 第 17 行 — 明文 Redis 密码、JWT 密钥(文件名含空格,会以纯文本返回)
- api/db_config.local.php 第 11/15/21 行 — 明文 DB 密码、JWT_SECRET、Redis 密码
- haoping.sql 第 6056 行起 — 全部用户密码哈希、手机号
风险:任何人通过浏览器直接访问 https://域 名/haoping.sql 即可下载,等同服务器被完全接管。
解决方案:
- 立即删除 上述 3 个文件(或移出 Web 根目录到
/www/secure/) - 立即修改 所有被泄露密码:宝塔面板、Debian root、MySQL、Redis、JWT_SECRET
- 强制重置所有用户密码(哈希已泄露)
- 在
.gitignore中排除*.sql、db_config*.php、*.local.php
P0-2:微信支付私钥 PEM 文件可被直接下载
问题文件:
- admin/apiclient_key/super/apiclient_key.pem
- admin/apiclient_key/brand/1/apiclient_key.pem
- admin/apiclient_key/super/pub_key.pem
风险:/admin/ 目录无 .htaccess 保护,攻击者直接 URL 下载私钥即可伪造任意支付/退款/分账请求。
解决方案:
- 将 PEM 文件移到 Web 根目录外(如
/www/secure_keys/) - 更新数据库
payment_configs.private_key_path为新路径 - 重置微信支付商户 API 证书(旧私钥视为已泄露)
- 在
/admin/.htaccess添加:
<FilesMatch "\.(pem|key|sql|log|lock)$">
Require all denied
</FilesMatch>
P0-3:缺失全部 HTTP 安全响应头 + 无 HTTPS 强制跳转
风险:无 X-Frame-Options → 点击劫持;无 HSTS → 中间人降级攻击;HTTP 不跳转 HTTPS → Cookie 明文传输。
解决方案:在 Database::jsonOut 和所有 HTML 入口统一添加:
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
在 Web 服务器 80 端口配置 301 跳转到 443。
P0-4:logs/、cache/、cron/ 目录可被直接访问
问题:
- logs/ 目录无
.htaccess,日志含 IP、UA、业务数据 - cache/security/ 存登录失败计数文件,文件名含 md5(手机号)
- cron/profit_split.php 等多个 cron 脚本无 CLI 模式校验,可被 HTTP 触发
解决方案:
- 在
logs/、cache/、cron/各创建.htaccess:Require all denied - 所有 cron 脚本开头添加:
if (php_sapi_name() !== 'cli') { http_response_code(403); exit; }
需修复的 cron 文件:profit_split.php、saas_invoice_generate.php、saas_expire_check.php、renewal_reminder.php(对比已正确的 stats_flush.php:42)。
P1 高危问题
P1-1:退出登录未失效 Token
问题:logout 仅返回成功,未执行 UPDATE users SET token_invalidated_at = NOW()。退出后 JWT 仍有效 24 小时。
解决方案:
case 'logout':
$db->execute(
'UPDATE users SET token_invalidated_at = NOW() WHERE id = ?',
[$currentUser['user_id']]
);
Database::jsonSuccess([], '已退出登录');
break;
P1-2:JWT 存 localStorage + URL 传参 Token
文件:admin_common.js 多处、middleware.php:68-71
问题:XSS 可读取 localStorage 窃取 JWT;?token=xxx 会被记录在日志、Referer、浏览器历史。
解决方案(架构性,建议中期实施):
- 短期:修复所有 XSS 漏洞(见 P1-4),缩短 JWT 有效期至 2 小时
- 中期:JWT 改为
HttpOnly; Secure; SameSite=StrictCookie 存储;文件下载改用短期一次性下载令牌
P1-3:默认密码为手机号后 8 位
解决方案:改为随机生成 8-12 位密码(含大小写+数字),通过短信发送;强制首次登录改密。
P1-4:存储型 XSS(3 处高危)
问题:
- admin/shop/jiaocheng.html:270,353 — 教程内容直接 innerHTML
- static/js/manager.js:113,115 — manager_pages 的 name/text_content
- admin/super/system.html:599,618 — 菜品名
解决方案:
- 教程内容(富文本):服务端引入 HTML Purifier 过滤;前端用
DOMPurify.sanitize()后再 innerHTML - 纯文本字段(菜品名、品牌名、page.name):前端渲染时调用
escapeHtml()
P1-5:IDOR 越权 — 4 个接口缺失 else 兜底
问题文件:
- shop_api.php:202-224
detail接口 - manager_pages_api.php:58-141
list接口 - jiaocheng_api.php:32-134
jiaocheng_list - jiaocheng_api.php:776-827
jiaocheng_group_available_articles
问题:权限校验 if/elseif 链无 else 兜底,promoter 角色或 shop_id 为空时 WHERE 条件为空,返回全表数据。
解决方案:在每个权限分支链末尾添加:
} else {
Database::jsonError('权限不足', 403);
}
shop_manager 分支中 shopId 为空时直接 403。
P1-6:文件上传缺少图片内容真实性校验
文件:ImageManager.php 第 98、392、628 行(3 个方法)
问题:仅 finfo_file 检测 magic bytes,可被 polyglot 文件绕过(头部图片+尾部 PHP 代码)。
解决方案:在 move_uploaded_file 前添加:
$imgInfo = @getimagesize($file['tmp_name']);
if ($imgInfo === false) {
return ['success' => false, 'message' => '非有效图片文件'];
}
// 可选:用 GD 重新渲染清除元数据
$img = imagecreatefromstring(file_get_contents($file['tmp_name']));
if ($img !== false) {
imagejpeg($img, $file['tmp_name'], 90);
imagedestroy($img);
}
P1-7:SSRF — download_image DNS Rebinding
问题:gethostbyname 解析后 curl_exec 会再次解析 DNS,攻击者可 DNS rebinding 访问内网。
解决方案:用 CURLOPT_RESOLVE 固定 DNS 结果:
$ip = gethostbyname($host);
// ... IP 校验后 ...
curl_setopt($ch, CURLOPT_RESOLVE, [$host . ':443:' . $ip, $host . ':80:' . $ip]);
curl_setopt($ch, CURLOPT_URL, $imageUrl);
P1-8:api_v3_key 明文存储(与表注释"加密存储"矛盾)
文件:saas_config_api.php:187-194
解决方案:参考 RemoteImageService::encrypt()/decrypt(),入库前 AES-256-CBC 加密,读取时解密。使用独立密钥(非 JWT_SECRET)。
P1-9:private_key_path 未做白名单校验
文件:saas_config_api.php:167,191-193
解决方案:
$privateKeyPath = $_POST['private_key_path'] ?? '';
if (!empty($privateKeyPath)) {
$realPath = realpath($privateKeyPath);
$allowedDir = realpath(__DIR__ . '/../admin/apiclient_key');
if ($realPath === false || strpos($realPath, $allowedDir . DIRECTORY_SEPARATOR) !== 0) {
Database::jsonError('私钥路径必须在 admin/apiclient_key 目录下');
}
// ... 存储校验后的路径
}
P1-10:RemoteImageService 硬编码密钥回退
问题:JWT_SECRET 未定义时回退到硬编码 'C@nYinV6_SuperSecret_Key_2026!@#',可解密所有云存储 AccessKey Secret。
解决方案:删除回退,未定义时抛异常。同样修复 JiaochengService.php:31、JiaochengGroupService.php:35。
P2 中危问题(21 项)
安全随机数(5 处 mt_rand → random_int)
| 文件 | 行号 | 用途 | 修复 |
|---|---|---|---|
| WechatPay.php | 688-696 | 支付签名 nonce | random_int() |
| WechatPay.php | 370,589 | 退款单号/订单号 | random_int() |
| ProfitSharingService.php | 261 | 分账单号 | random_int() |
| AliyunSms.php | 216 | 签名防重放 | bin2hex(random_bytes(16)) |
| jiaocheng_api.php | 875,940,1075 | slug 生成 | bin2hex(random_bytes(3)) |
CSRF 相关(3 项)
- 文件上传绕过 CSRF — middleware.php:48-55:移除
$isFileUpload旁路,文件上传也校验 CSRF(通过表单隐藏字段_csrf_token) - 提现缺二次验证 — saas_promotion_api.php:428-473:增加短信验证码或密码确认
- 修改密码强度不一致 — auth_api.php:256-258:
change_password也执行强密码校验(与forgot_password一致)
路径遍历(4 处 strpos 前缀校验可绕过)
文件:saas_invoice_api.php:84、images_api.php:561、qrcode_api.php:502、public_api.php:433
修复:
// 错误:strpos($fullPath, $allowedDir) !== 0
// 正确:
if ($fullPath !== $allowedDir && strpos($fullPath, $allowedDir . DIRECTORY_SEPARATOR) !== 0) {
Database::jsonError('无效的文件路径', 403);
}
信息泄露(4 项)
- API 异常返回 $e->getMessage() — 19 处(
saas_config_api.php16 处、operation_api.php:524、jiaocheng_api.php:427、public_api.php:1139):改为通用错误提示,详情仅error_log - ai_config_api.php SELECT * — 第 34-35 行:改为白名单字段,
api_key返回掩码sk-****1234 - 登录响应泄露用户名存在性 — auth_api.php:137-141:
show_forgot_password统一返回 - 公开接口可枚举门店 — public_api.php:88-114:增加 IP 速率限制;phone 字段脱敏
数据存储安全(3 项)
- SMS 验证码明文存储 — sms_api.php:160-163:入库前
password_hash(),校验用password_verify() - 教程访问密码明文存储 — jiaocheng_api.php:231-232:改用
password_hash(),管理端改为重置而非回显 - TencentVideoService SSL 校验禁用 — TencentVideoService.php:111-122:移除
CURLOPT_SSL_VERIFYPEER => false
其他中危(2 项)
- 授权域名校验用 HTTP_HOST — verify_license.php:190-194:改用
SERVER_NAME - Redis 密码强度不足 — 使用 32+ 位随机字符串,启用 ACL
P3 低危问题(15 项,简列)
| 问题 | 文件 | 修复 |
|---|---|---|
| LIKE 通配符未转义 | jiaocheng_api.php:814 等 3 处 | str_replace(['\\','%','_'],['\\\\','\\%','\\_'],$kw) |
| 图片文件名用 rand() | ImageManager.php 3 处 | bin2hex(random_bytes(4)) |
| ImageManager delete 无 realpath 校验 | ImageManager.php:137 | 删除前 realpath 校验 |
| WechatPay 私钥路径未校验 | WechatPay.php:143 | 同 P1-9 |
| qrcode LOGO 拉取 SSRF | qrcode_api.php:574 | 同 P1-7 |
| 凭证上传允许 PDF | saas_promotion_api.php:987 | 下载时 Content-Disposition: attachment |
| 跳转参数校验不严 | admin/index.html:181 | 增加 /\ 开头拒绝 |
| OSS 端点管理员可控 | RemoteImageService.php:490 | 域名白名单校验 |
| 无 Referer/Origin 校验 | 全项目 | 写操作增加 Origin 校验 |
| Cron 输出堆栈信息 | profit_split.php:116 | 写入独立日志文件 |
| verify_license 调试入口 | verify_license.php:335 | 删除直接访问分支 |
| db_config 缺失返回路径 | db_config.php:33 | 改为通用 500 错误 |
| 微信证书缓存权限 0755 | WechatPay.php:491 | 改为 0700 |
| phpqrcode echo 异常 | phpqrcode.php:309 | 严格捕获输出 |
| 缺少 php.ini 配置文档 | 全项目 | 补充部署文档 |
修复优先级建议
第一阶段(立即,24 小时内):P0 全部 4 类 — 删除敏感文件、移动私钥、加安全头、加目录保护
第二阶段(1 周内):P1 全部 10 项 — logout 失效 token、修复 XSS、补 else 兜底、图片内容校验、SSRF 修复、密钥加密
第三阶段(1 月内):P2 中危 21 项 — 随机数升级、CSRF 强化、路径校验、信息泄露
第四阶段(迭代中):P3 低危 15 项
重要提醒:P0-1 和 P0-2 涉及的密码/密钥/证书已视为泄露,仅删除文件不够,必须轮换所有凭证(服务器密码、MySQL、Redis、JWT_SECRET、微信支付证书)。
需要我开始按优先级修复这些问题吗?建议从 P0 开始。