如何防止通过浏览器开发者工具伪造 AJAX 请求提交恶意数据

1次阅读

如何防止通过浏览器开发者工具伪造 AJAX 请求提交恶意数据

本文介绍在不依赖框架的前提下,通过服务端验证与客户端数据结构重构,从根本上防止用户利用浏览器控制台伪造 ajax 请求篡改游戏分数等关键业务数据。核心思路是:不信任前端传来的最终结果,而是由后端基于可信操作过程重新计算并校验。

本文介绍在不依赖框架的前提下,通过服务端验证与客户端数据结构重构,从根本上防止用户利用浏览器控制台伪造 ajax 请求篡改游戏分数等关键业务数据。核心思路是:不信任前端传来的最终结果,而是由后端基于可信操作过程重新计算并校验。

在纯前端实现的记忆游戏中,常见做法是 JavaScript 计算出最终得分(如 totalScore)后,直接通过 $.post() 提交用户名与分数至 PHP 后端保存。然而,这种设计存在严重安全隐患:任何熟悉基础 jquery 的用户,只需在浏览器开发者工具中执行一行代码,即可绕过游戏逻辑,任意提交高分:

$.post("./backend/save_score.php", {"player_name": "Hacker", "score": 999999999});

仅靠前端限制(如禁用控制台、混淆代码、添加时间戳或随机 Token)无法真正解决问题——因为所有前端逻辑均可被审查、调试与重放。真正的防护必须落在服务端。

✅ 正确方案:传递“过程”,而非“结果”

将前端职责从「计算并上报分数」转变为「记录并上报用户真实操作序列」,由 PHP 后端独立复现游戏逻辑、验证操作合法性,并最终生成可信分数。例如:

1. 前端:记录并发送操作日志(而非分数)

// 示例:记录每次翻牌的卡片 ID 和时间戳(可选) const gameActions = [];  function onCardFlip(cardId) {   gameActions.push({     action: "flip",     cardId: cardId,     timestamp: Date.now()   }); }  $("#submitScore").on("click", function() {   const playerName = $('#playerName').val().trim() || "Unknown";    // 发送完整操作序列,不含 score 字段   const payload = {     player_name: playerName,     actions: gameActions,  // 如 [{action:"flip", cardId:"c3"}, ...]     game_version: "1.0"    // 防止旧版客户端绕过新验证逻辑   };    $.post("./backend/save_score.php", payload, function(response) {     if (response.success) {       fetchHighScores();       $("#submitScore").prop('disabled', true);     } else {       alert("提交失败:" + response.error);     }   }).fail(() => alert("网络错误,请重试")); });

2. 后端(save_score.php):严格校验 + 重算分数

<?php header('Content-Type: application/json; charset=utf-8'); session_start();  // 简单防重复提交(可选) if (isset($_SESSION['score_submitted']) && time() - $_SESSION['score_submitted'] < 5) {     echo json_encode(['success' => false, 'error' => '请求过于频繁']);     exit; }  $data = json_decode(file_get_contents('php://input'), true) ?: $_POST; $actions = $data['actions'] ?? []; $playerName = filter_var($data['player_name'] ?? '', FILTER_SANITIZE_STRING);  // ✅ 关键校验步骤: // 1. 检查 actions 是否为空或格式非法 if (!is_array($actions) || count($actions) === 0) {     echo json_encode(['success' => false, 'error' => '无效的操作记录']);     exit; }  // 2. 校验操作序列是否符合游戏规则(示例逻辑) $validCards = ['c1', 'c2', 'c3', 'c4', 'c5', 'c6']; // 实际应从数据库/配置加载 $flippedPairs = []; $score = 0;  foreach ($actions as $action) {     if ($action['action'] !== 'flip' || !in_array($action['cardId'], $validCards)) {         echo json_encode(['success' => false, 'error' => '检测到非法操作']);         exit;     }      // 模拟配对逻辑:每成功一对得 10 分(实际按你的规则实现)     $flippedPairs[] = $action['cardId'];     if (count($flippedPairs) === 2) {         if ($flippedPairs[0] === $flippedPairs[1]) {             $score += 10;         }         $flippedPairs = []; // 重置     } }  // 3. 可选:添加时间合理性检查(如总耗时过短则质疑作弊) $duration = (end($actions)['timestamp'] ?? 0) - ($actions[0]['timestamp'] ?? 0); if ($duration < 1000) { // 少于 1 秒完成?极大概率作弊     error_log("Suspiciously fast game: {$playerName}, {$duration}ms");     echo json_encode(['success' => false, 'error' => '游戏时间异常,拒绝提交']);     exit; }  // ✅ 安全入库:仅使用后端计算的 $score try {     $pdo = new PDO("mysql:host=localhost;dbname=memory_game", $user, $pass);     $stmt = $pdo->prepare("INSERT INTO scores (player_name, score, created_at) VALUES (?, ?, NOW())");     $stmt->execute([$playerName, $score]);     $_SESSION['score_submitted'] = time();     echo json_encode(['success' => true, 'score' => $score]); } catch (Exception $e) {     error_log("DB save failed: " . $e->getMessage());     echo json_encode(['success' => false, 'error' => '服务器内部错误']); }

⚠️ 重要注意事项

  • 永远不要信任 $_POST 中的 score、level、coins 等数值型结果字段,它们必须由服务端基于可验证行为重新生成;
  • 前端可增加轻量级辅助校验(如操作次数上限、最小耗时提示),但仅作用户体验优化,不可作为安全依据
  • 对敏感接口建议添加简单 Token 机制(如一次性 form_token 存入 session 并随请求提交),防御 csrf(虽非本问题核心,但属良好实践);
  • 日志记录异常请求(如高频提交、超低耗时、非法 cardId),便于后续分析攻击模式。

通过将「分数计算权」完全移交服务端,并以可审计的操作序列为输入,你构建的不再是一个易被操控的“结果提交接口”,而是一个具备业务逻辑自证能力的「游戏过程验证服务」——这才是无框架环境下最坚实、最可持续的安全防线。

text=ZqhQzanResources