PHP 怎么防积分漏洞

wen PHP项目 7

本文目录导读:

PHP 怎么防积分漏洞

  1. 服务端强校验(核心防线)
  2. 防重放与并发控制
  3. 频率限制与风控
  4. 安全编码(防SQL注入)
  5. 交易日志与审计
  6. 特例:防止负数提现(负余额攻击)
  7. 服务器安全配置
  8. 典型漏洞排查清单

PHP防止积分漏洞是一个综合性的安全问题,核心在于所有涉及积分的操作都必须经过服务端校验,并且遵循“不信任任何前端输入”的原则。

以下是PHP开发中防范积分漏洞的几大核心策略,按优先级排列:

服务端强校验(核心防线)

前端传值(如 amount=100points=9999)是绝对不能信任的,为了防止篡改,积分变动逻辑必须放在服务端,并且金额/积分数值必须在服务端计算得出,或者仅使用前端传的ID作为索引。

  • 反例(危险)

    // 直接从POST获取数量
    $points = $_POST['points'];
    $sql = "UPDATE users SET points = points + $points WHERE id = $user_id";

    攻击者把 points 改为 999999 就能直接刷分。

  • 正例(安全)

    // 前端的ID只作为商品ID引用
    $item_id = (int)$_POST['item_id'];
    // 从数据库查询该商品的真实价格
    $item = $db->query("SELECT * FROM items WHERE id = $item_id AND status=1")->fetch();
    if (!$item) { die('商品不存在'); }
    // 在服务端计算积分
    $earned_points = ceil($item['price'] * 0.1); // 代码逻辑决定积分
    $sql = "UPDATE users SET points = points + $earned_points WHERE id = $user_id";

防重放与并发控制

积分漏洞中最常见的是并发刷积分(利用多次点击)和重放攻击(抓包后重复发送请求)。

  • 幂等性设计:每个积分操作必须携带一个唯一的 order_no(业务订单号)或 nonce(随机数),数据库对该字段设置唯一索引,重复提交会被数据库拒绝。

    $order_no = $_POST['order_no'];
    // 尝试插入记录,如果order_no已存在,插入会失败
    try {
        $db->beginTransaction();
        $db->exec("INSERT INTO points_log (user_id, order_no, points) VALUES ($uid, '$order_no', $points)");
        $db->exec("UPDATE users SET points = points + $points WHERE id = $uid");
        $db->commit();
    } catch (Exception $e) {
        $db->rollBack();
        // 记录重复点击或重放攻击
    }
  • Redis/数据库行锁:在高并发下,一定要使用事务或 SELECT ... FOR UPDATE 锁住用户行,防止多个并发请求同时读取到旧的积分值,导致积分覆盖(竞态条件)。

    $db->beginTransaction();
    // 锁住该用户的行
    $user = $db->query("SELECT points FROM users WHERE id = 1 FOR UPDATE")->fetch();
    $new_points = $user['points'] + 100;
    $db->exec("UPDATE users SET points = $new_points WHERE id = 1");
    $db->commit();

频率限制与风控

即使接口是安全的,也需要防刷子。

  • 限流:使用 Laravel 的 ThrottleRequests 或 Redis 计数器限制接口调用频率(每分钟最多领取1次,每小时最多兑换10次)。
  • 行为验证:涉及领取优惠券、抽奖等高风险积分操作时,接入 Google reCAPTCHA 或极验验证码,防止机器自动点击。
  • IP/设备指纹:监控频繁变更账号且来自同一IP或设备号的异常行为。

安全编码(防SQL注入)

不要将用户输入直接拼接进SQL查询,积分漏洞往往伴随着SQL注入,一旦注入成功,攻击者可以直接改数据库。

  • 必须使用预处理语句(PDO)
    $stmt = $pdo->prepare("UPDATE users SET points = points + ? WHERE id = ?");
    $stmt->execute([$points, $user_id]);

交易日志与审计

所有积分变动(增加、减少、冻结)都必须产生不可篡改的日志。

  • 日志字段iduser_idpoints_change(正负)、operation_type(如注册/下单/签到/提现)、ref_id(来源ID)、ipuser_agentcreated_at
  • 对账机制:定期跑脚本,校验 用户积分总数 是否等于 积分流水日志的和,如果对不上,说明有攻击发生,可以迅速定位漏洞点。

特例:防止负数提现(负余额攻击)

如果用户余额不足,系统必须能拦住提现请求。

  • 事务内更新余额时,SQL条件必须包含余额足够判断。
    -- 只有余额大于等于扣款金额时才更新,affected_rows 为0表示余额不足
    UPDATE users SET points = points - 100 
    WHERE id = 1 AND points >= 100;
    $result = $db->exec($sql);
    if ($result == 1) {
        // 扣款成功
    } else {
        // 余额不足或用户不存在
    }

服务器安全配置

  • 关闭 PHP 的 display_errors,防止报错信息泄露 SQL 语句结构。
  • 对后台管理接口(如调整积分接口)进行严格的身份权限校验(RBAC),防止越权调用。

典型漏洞排查清单

如果怀疑已经被刷积分,请按以下顺序排查:

  1. 查日志:是否有大量短时间内的积分请求?
  2. 查直接赋值:代码中是否直接使用了 $_GET$_POST 的数值来计算积分?
  3. 查重复提交:积分日志表是否有唯一约束?
  4. 查负数:是否有小于0的积分流水?是否允许负数?
  5. 查订单状态:是否对同一笔订单重复发放积分?

最核心的一句话“积分增量必须由服务端根据业务规则计算,且写入操作必须在数据库事务中完成,并对订单号建立唯一索引。”

抱歉,评论功能暂时关闭!