02-安全审查.md 12 KB

第二阶段:安全审查报告

审查概述

项目 详情
审查日期 2026年5月19日
审查范围 SQL注入、XSS、CSRF、认证授权、文件上传、支付安全、会话管理
发现问题数 47个
严重风险 12个
高风险 15个
中风险 14个
低风险 6个

1. SQL 注入风险

🔴 严重风险

1.1 用户输入直接拼接 SQL

文件mcpsapi/controller/ChannelRebind.php:74

$cusername = $this->input('cusername','','trim');
$whereRaw .= ' origin_cusername="'.$cusername.'" or cusername="'.$cusername.'" ';

风险:用户输入 $cusername 直接拼接到 SQL 语句,可直接进行 SQL 注入攻击

相同问题位置

  • guildapi/controller/ChannelRebind.php:74
  • admin/controller/MemberRebind.php:97

1.2 JOIN 条件直接拼接用户输入

文件api/controller/v2/Gift.php:206

->join('cy_libaoinfo a', 'll.infoid=a.id and ll.user_id="' . $this->input['userid'] . '"')

相同问题api/controller/v1/Gift.php:108

1.3 Db::query 使用变量插值

文件common/logic/Complex.php:166

$query = Db::query("SELECT SUM(total_amount*divide_point) as total FROM cy_polychannel_game_frozen WHERE `channel_id`={$channel_id} AND `status`=1");

🟡 中风险(约 50+ 处)

多个文件使用 whereRaw 配合变量插值但未参数绑定:

  • api/controller/v2/User.php:918
  • api/controller/v2/Gift.php:41
  • service/WelfareService.php 多处
  • admin/controller/Welfare*.php 多处
  • guildapi/controller/*.php 多处
  • mcpsapi/controller/*.php 多处

修复建议

// ❌ 错误写法
$whereRaw .= ' origin_cusername="'.$cusername.'"';

// ✅ 正确写法
$whereRaw .= ' origin_cusername=?';
$bindParams[] = $cusername;

2. 认证与授权

🔴 严重风险

2.1 GuildAPI GET 请求免密登录

文件guildapi/controller/Login.php:98-103

}else{
    $username = trim(input('get.username'));
    $adminInfo = Db::name('nw_channel_admin')->where(['username' => $username])->find();
    return $this->getdoLogin($adminInfo);  // 直接登录,无密码验证!
}

风险:通过 GET 请求仅传 username 即可直接登录,无需密码

2.2 权限检查逻辑缺陷 — 未注册规则默认放行

文件common/logic/Auth.php:46-48

if ($findAuthRuleCount == 0) {
    //return false;
    return true;  // ← 关键缺陷:未配置的路由默认放行
}

风险:任何未配置到菜单的控制器/操作都无需权限即可访问

2.3 密码使用 MD5 哈希

文件common.php:681-684

function mg_password($pw)
{
    return md5(Env::get('ADMIN_PASS_PRE') . $pw);
}

风险:MD5 已不安全,易被彩虹表攻击

🔴 高风险

2.4 密码修改存在越权风险(IDOR)

文件admin/controller/Login.php:206-253

$id = input('post.id','','intval');  // id 从客户端获取
$adminModel->where(['id'=>$id])->update(['password'=>mg_password($new_password)]);

风险:未校验是否为当前登录用户,可修改其他管理员密码

2.5 硬编码超级管理员 ID

文件common/logic/Auth.php:33

if ($userId == 1) { return true; }  // ID=1 直接跳过权限检查

相同位置admin/controller/Rbac.php:33, admin/controller/User.php:40

2.6 API 调试模式可绕过加密和签名

文件api/controller/Api.php:65-110

if (!$decryptStr || $api_debug == true) {
    $this->input = input();  // 直接使用明文参数,绕过 AES 和签名验证
}

风险:通过请求头 qmapidebug: true 可绕过所有安全验证

🟡 中风险

2.7 Admin 登录无暴力破解防护

文件admin/controller/Login.php:110-121

  • 无登录失败次数限制
  • 无 IP 限流
  • 无账号锁定机制

2.8 API 签名使用 MD5 + AES-ECB

文件api/controller/Api.php:154,204

$mysgin = md5($arg);  // MD5 签名强度不足
$str = openssl_encrypt($data, 'AES-128-ECB', $key);  // ECB 模式不安全

3. XSS 防护

🔴 严重风险

3.1 Admin 模板大量反射型 XSS

位置admin/view/652+ 处

以下模板直接输出未转义的用户输入:

<!-- admin/view/pay/list.html:189 -->
<input value="{$Request.get.orderid}">

<!-- admin/view/pay/list.html:195 -->
<input value="{$Request.get.username}">

<!-- admin/view/game_discount_v2/index.html:123 -->
<script>var initGameIds = '{$Request.get.game_id}'</script>

完整漏洞列表: | 文件 | 行号 | 变量 | |------|------|------| | admin/view/pay/list.html | 134,189,195,201,208 | orderid, username, roleid, rolename, servername | | admin/view/pay/index.html | 158,164 | orderid, username | | admin/view/member/login_list.html | 73,80 | username, imeil | | admin/view/member/bind_list.html | 55,63 | username, mobile | | admin/view/member/index.html | 163,170 | m_nickname, m_username | | admin/view/game_package/add.html | 165 | game_id | | admin/view/welfare_grant/index.html | 81 | username |

🟡 中风险

3.2 全局输入过滤为空

文件config.php:50

'default_filter' => ''  // 全局过滤为空

仅 home/mobile 模块有过滤

'default_filter' => 'trim,htmlspecialchars,addslashes,strip_tags'

admin/api/guildapi/mcpsapi 模块均无输入过滤

3.3 removeXss() 定义但未使用

文件common.php:109-122 — 定义了 HTMLPurifier 过滤函数,但从未被调用


4. CSRF 防护

🔴 严重风险

4.1 完全没有 CSRF 防护

检查项 状态
CSRF Token 生成/验证 ❌ 未实现
表单 __token 隐藏字段 ❌ 未使用
ThinkPHP 内置 CSRF 中间件 ❌ 未启用
任何表单中的 CSRF 隐藏字段 ❌ 完全没有

影响范围:所有 admin 后台表单均存在跨站请求伪造风险


5. 文件上传安全

🔴 严重风险

5.1 绕过框架直接使用 move_uploaded_file

文件admin/controller/GamePackage.php:341

$result = move_uploaded_file($_FILES['data']['tmp_name'], iconv('UTF-8', 'GBK', $filename));

风险

  • 仅检查后缀名是否为 ['ipa','apk']
  • 无 MIME 类型校验
  • 无文件内容校验(magic bytes)
  • 目录权限 0777(第335行)

相同问题admin/controller/GameUpload.php:46

5.2 Base64 图片上传类型校验可绕过

文件home/controller/Member.php:1320-1352

if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)){
    $type = $result[2];  // 仅校验 base64 头部声明,不校验实际内容

风险

  • 可伪造头部上传恶意文件
  • 文件写入到 RUNTIME_PATH 目录

🟡 中风险

5.3 上传目录无执行限制

  • public/.htaccess 文件为空
  • 上传目录没有 .htaccess 禁止 PHP 执行
  • 如果上传了 .php 文件可直接执行

5.4 后缀名校验逻辑缺陷

文件admin/controller/GamePackage.php:313-316

  • 未考虑大小写(如 .APK
  • 未考虑双扩展名(如 .apk.php

6. 支付安全

🔴 严重风险

6.1 硬编码支付密钥

文件service/PayService.php:575

$params['sign'] = $wxPay->MakeSign($params, '66052a198653943dbc6c8032f1a40a9c');

相同问题

  • common/factory/pay/SywPay.php:107,203
  • common/logic/PayCallback.php:182

6.2 SSL 证书验证禁用(全局 51 处)

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

位置

  • common.php:373-374,884-885
  • service/PayService.php:880-881,914-915
  • common/library/WeixinPay.php:85-88

6.3 金额精度问题

文件common.php:1475-1489

function formatFenToYuan($fen, $long = 2) {
    return number_format($fen / 100, $long, '.', ''); // 浮点数除法可能丢失精度
}

🟡 中风险

6.4 缺少重放攻击防护

  • 回调接口无 nonce/timestamp 验证
  • 无请求频率限制

6.5 订单状态竞态条件

文件api/controller/v1/PayNotify.php:950-951

$payInfo = $payModel->where(['orderid' => $orderid])->find();
if ($payInfo['status'] == 1 && $payInfo['real_amount'] == $real_amount) {
    return true; // 先查询再更新,存在并发风险
}

6.6 签名算法安全性低

  • 多处使用 MD5 签名
  • PayNotify.php:367 - md5($str . md5($key))

7. 其他安全问题

🟡 中风险

7.1 Cookie 安全配置缺失

文件config.php:286-290

'secure' => false,    // 允许 HTTP 明文传输
'httponly' => '',      // JS 可读取 Cookie

7.2 CORS 配置过于宽松

文件public/index.php:14, guildapi/controller/Guild.php:70

header('Access-Control-Allow-Origin: *');  // 允许任意来源

7.3 安全响应头完全缺失

未设置以下安全头:

  • Content-Security-Policy
  • X-Frame-Options
  • X-XSS-Protection
  • X-Content-Type-Options
  • Strict-Transport-Security

7.4 默认开启调试模式

文件config.php:24

'app_debug' => Env::get('app_debug', true)  // 默认开启

8. 问题汇总

按风险等级分类

🔴 严重风险(需立即修复)

序号 问题 位置
1 SQL 注入 - 用户输入直接拼接 mcpsapi/controller/ChannelRebind.php:74
2 SQL 注入 - JOIN 条件拼接 api/controller/v2/Gift.php:206
3 GuildAPI GET 免密登录 guildapi/controller/Login.php:98-103
4 权限未注册规则默认放行 common/logic/Auth.php:46-48
5 密码使用 MD5 哈希 common.php:681-684
6 Admin 模板 652+ 处 XSS admin/view/ 多处
7 完全没有 CSRF 防护 全局
8 文件上传绕过框架 admin/controller/GamePackage.php:341
9 硬编码支付密钥 service/PayService.php:575
10 SSL 证书验证禁用(51处) common.php
11 API 调试模式绕过验证 api/controller/Api.php:65-110
12 Base64 上传校验可绕过 home/controller/Member.php:1320

🔴 高风险

序号 问题 位置
1 密码修改 IDOR 越权 admin/controller/Login.php:206
2 硬编码超管 ID common/logic/Auth.php:33
3 SQL 注入 - Db::query 变量插值 common/logic/Complex.php:166
4 上传目录 0777 权限 admin/controller/GamePackage.php:335
5 金额精度问题 common.php:1475-1489

🟡 中风险

序号 问题 数量
1 whereRaw 未参数绑定 约 50+ 处
2 登录无暴力破解防护 1 处
3 API 签名 MD5+ECB 多处
4 缺少重放攻击防护 全局
5 订单竞态条件 1 处
6 Cookie 配置不安全 全局
7 CORS 过于宽松 2 处
8 安全响应头缺失 全局
9 全局输入过滤为空 1 处
10 上传目录无执行限制 全局

9. 修复优先级

第一优先级(立即修复 - 安全漏洞)

  1. 修复 SQL 注入高风险点(参数绑定)
  2. 删除 GuildAPI GET 免密登录
  3. 修复权限检查逻辑(return false
  4. 修复 Admin 模板 XSS(转义输出)
  5. 添加 CSRF Token
  6. 修复文件上传安全问题

第二优先级(尽快修复)

  1. 密码哈希升级为 bcrypt/argon2
  2. 限制 CORS 来源
  3. 启用 SSL 证书验证
  4. 添加安全响应头
  5. 密钥从环境变量读取
  6. 禁用生产环境调试模式

第三优先级(计划修复)

  1. 升级签名算法(HMAC-SHA256)
  2. 添加登录暴力破解防护
  3. 添加重放攻击防护
  4. 金额处理改用 bcmath
  5. 完善日志脱敏

10. 参考代码

SQL 注入修复示例

// ❌ 错误
$whereRaw .= ' origin_cusername="'.$cusername.'"';
Db::query("SELECT * WHERE id={$id}");

// ✅ 正确
$whereRaw .= ' origin_cusername=?';
$bindParams[] = $cusername;
Db::query("SELECT * WHERE id=?", [$id]);

XSS 修复示例

<!-- ❌ 错误 -->
<input value="{$Request.get.username}">

<!-- ✅ 正确 -->
<input value="{:escape($Request.get.username)}">

密码哈希修复示例

// ❌ 错误
return md5($prefix . $password);

// ✅ 正确
return password_hash($password, PASSWORD_DEFAULT);
// 验证
return password_verify($input, $hashedPassword);

CSRF Token 示例

<form>
    {csrf_field()}
    <!-- 或 -->
    <input type="hidden" name="__token" value="{$Request.token}">
</form>

审查报告生成时间:2026年5月19日