本文目录导读:

PHP防止积分漏洞是一个综合性的安全问题,核心在于所有涉及积分的操作都必须经过服务端校验,并且遵循“不信任任何前端输入”的原则。
以下是PHP开发中防范积分漏洞的几大核心策略,按优先级排列:
服务端强校验(核心防线)
前端传值(如 amount=100、points=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]);
交易日志与审计
所有积分变动(增加、减少、冻结)都必须产生不可篡改的日志。
- 日志字段:
id、user_id、points_change(正负)、operation_type(如注册/下单/签到/提现)、ref_id(来源ID)、ip、user_agent、created_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),防止越权调用。
典型漏洞排查清单
如果怀疑已经被刷积分,请按以下顺序排查:
- 查日志:是否有大量短时间内的积分请求?
- 查直接赋值:代码中是否直接使用了
$_GET或$_POST的数值来计算积分? - 查重复提交:积分日志表是否有唯一约束?
- 查负数:是否有小于0的积分流水?是否允许负数?
- 查订单状态:是否对同一笔订单重复发放积分?
最核心的一句话:“积分增量必须由服务端根据业务规则计算,且写入操作必须在数据库事务中完成,并对订单号建立唯一索引。”