卡卷网
当前位置:卡卷网 / 每日看点 / 正文

大家在做登录功能时,一般怎么做暴力破解防护?

作者:卡卷网发布时间:2025-01-19 19:13浏览数量:307次评论数量:0次

防爆破有一个统一的方法来对抗, 那就是: 工作量证明(Proof of Work).

工作量证明并不复杂: 发起请求前让客户端先算一个十分烧CPU的值-比如说我要找出某个哈希值, 它的前六位必须全是0.

很多人或许听过它在区块链上的应用, 比如比特币挖矿. 其实, 工作量证明的核心思想也能用来对付各种"爆破式攻击".

客户端在发请求前必须先做一段计算, 而服务端只要简单验证就行.

这相当于是说:"老兄, 你想进来可以, 但得先把这一大堆数字给我算完, 再告诉我结果." 而这个计算过程对用户来说, 要消耗CPU, 时间, 电量, 算下来就有了成本. 然后服务端验证却很快, 只需要轻轻计算一次哈希就能知道你算得对不对.

这样一来, 正常用户虽然多花点时间, 几百毫秒, 但还能接受; 可攻击者要是一口气想发几百万次的请求, 攻击者就得老老实实把这一大堆计算都做了-成本瞬间飙升.

工作量证明的优点:

  1. 验证成本低: 服务端只算一次哈希就能验证. 约耗费1ms即可.
  2. 攻击成本高: 攻击者想发海量请求, 计算量会成倍增加. 单次请求花费500ms计算,百万次请求几乎就是难以承担的计算成本了.
  3. 调节灵活: 修改难度, 即可控制一次计算的耗时.

当然, 它也不是万能的. 如果有人有钱又肯烧电, 还是能硬扛. 因此, 实际应用时, 通常还会搭配限速, IP封禁, 验证码, 多因素认证等其他策略.

总之, 工作量证明很适合那些容易被批量尝试的接口. 如果你遇到暴力轰炸或穷举攻击, 不妨试试这个方法. 虽然用户会多花些时间, 但能有效降低爆破的成功率. 毕竟, 算点哈希, 胜过被无限次轰炸.

下面是一份带有注释的单文件示例代码, 展示了如何在Deno环境下使用工作量证明(Proof of Work)来对抗爆破式攻击. 其中使用了node:crypto来计算SHA-256, 这些哈希计算逻辑也可以在Node.js中直接复用.

使用方式:

  1. 将这份代码保存为server.ts.
  2. 在终端运行: deno run --allow-net server.ts
  3. 打开浏览器访问http://localhost:8000/查看页面, 点击按钮执行POW计算并向后端验证.

import { serve } from "https://deno.land/std@0.209.0/http/server.ts"; // node:crypto在Deno中也能用, 同样适用于Node.js, 因此哈希计算逻辑可复用 import { createHash } from "node:crypto"; /** * 这是一个前后端同文件示例: * - 当访问根路径"/"时, 会返回内置的HTML页面(HTML_PAGE变量). * - /challenge 用来提供challenge(随机字符串)和难度difficulty. * - /verify 用来验证客户端提交的(challenge + nonce)是否满足难度要求. */ // —— 前端HTML + JS的内容, 放在一个字符串常量里. —— const HTML_PAGE = `<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Proof of Work (Single File Demo)</title> <style> body { font-family: sans-serif; margin: 20px; } #logArea { margin-top: 20px; padding: 10px; background: #f7f7f7; border: 1px solid #ccc; white-space: pre; font-family: monospace; } #status { margin-top: 10px; color: blue; font-weight: bold; } button { padding: 8px 16px; } </style> </head> <body> <h1>Proof of Work (Single File Demo)</h1> <button id="powButton">Compute POW and Verify</button> <p id="status"></p> <div id="logArea"></div> <script> // 获取页面元素 const logArea = document.getElementById('logArea'); const statusEl = document.getElementById('status'); /** * 前端的统一日志打印函数 * @param {string} level - 日志级别, 如"INFO","WARN","ERROR" * @param {string} message - 具体日志内容 */ function frontLog(level, message) { // 按[INFO] [WARN] [ERROR]的格式打印 const logMessage = \`[\${level}] \${message}\`; console.log(logMessage); // 同时输出到浏览器控制台 logArea.textContent += logMessage + "\\n"; // 显示到网页上的日志区域 } /** * 将 ArrayBuffer 转成十六进制字符串, 以便查看哈希结果 * @param {ArrayBuffer} buffer - 浏览器端计算的哈希结果(二进制) * @returns {string} - 对应的hex字符串 */ function bufferToHex(buffer) { const hexCodes = []; const view = new DataView(buffer); for (let i = 0; i < view.byteLength; i++) { // 将每个字节转成两位16进制表示 const value = view.getUint8(i).toString(16).padStart(2, "0"); hexCodes.push(value); } return hexCodes.join(""); } /** * 计算字符串的SHA-256哈希(浏览器端) * @param {string} str - 待哈希的字符串 * @returns {Promise<string>} - hex格式的哈希值 */ async function sha256(str) { const encoder = new TextEncoder(); const data = encoder.encode(str); // crypto.subtle.digest在浏览器端计算SHA-256, 返回ArrayBuffer const hashBuffer = await crypto.subtle.digest("SHA-256", data); return bufferToHex(hashBuffer); } /** * 前端执行POW: 不断尝试nonce, 找到一个令hash前difficulty位(16进制)为"0"的nonce * @param {string} challenge - 服务端下发的随机字符串 * @param {number} difficulty - 难度, 需要hash前多少位为"0" * @returns {Promise<number>} - 找到的nonce */ async function computeProofOfWork(challenge, difficulty) { const targetPrefix = "0".repeat(difficulty); const startTime = performance.now(); // 记录开始时间 let nonce = 0; while (true) { // 计算hash const hashValue = await sha256(challenge + nonce); // 判断hash的前difficulty个字符是否是"0" if (hashValue.startsWith(targetPrefix)) { // 算到满足条件后, 计算耗时 const endTime = performance.now(); const costMs = (endTime - startTime).toFixed(2); frontLog("INFO", \`PoW computation finished. nonce=\${nonce}, cost=\${costMs} ms\`); return nonce; } nonce++; } } /** * 处理"Compute POW and Verify"按钮的点击事件 * 1. 请求/challenge获取challenge和difficulty * 2. 前端计算PoW * 3. 提交到/verify进行验证 */ document.getElementById("powButton").addEventListener("click", async () => { // 每次点击按钮, 先清空前端日志显示 logArea.textContent = ""; statusEl.textContent = "请求 challenge 中..."; frontLog("INFO", "REQUEST /challenge -> 获取challenge和difficulty"); // 记录获取challenge的开始时间 const challengeStart = performance.now(); const challengeResp = await fetch("/challenge"); const { challenge, difficulty } = await challengeResp.json(); // 获取challenge结束 const challengeEnd = performance.now(); frontLog("INFO", \`RESPONSE /challenge, 耗时=\${(challengeEnd - challengeStart).toFixed(2)} ms -> challenge="\${challenge}", difficulty=\${difficulty}\`); // 开始计算POW statusEl.textContent = \`开始计算 POW, 难度 = \${difficulty}\`; frontLog("INFO", \`开始PoW计算, difficulty=\${difficulty}, challenge="\${challenge}"\`); const nonce = await computeProofOfWork(challenge, difficulty); // 计算完成后, 请求后端验证 statusEl.textContent = \`POW计算完成 (nonce = \${nonce}), 开始验证...\`; frontLog("INFO", \`REQUEST /verify -> 提交 {challenge="\${challenge}", nonce=\${nonce}, difficulty=\${difficulty}}\`); const verifyStart = performance.now(); const verifyResp = await fetch("/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ challenge, nonce, difficulty }) }); const result = await verifyResp.json(); const verifyEnd = performance.now(); frontLog("INFO", \`RESPONSE /verify, 耗时=\${(verifyEnd - verifyStart).toFixed(2)} ms -> success=\${result.success}\`); // 更新页面状态 statusEl.textContent = result.success ? "验证成功!" : ("验证失败: " + (result.error || "未知错误")); }); </script> </body> </html> `; /** * 用于验证客户端提交的(challenge + nonce)是否满足难度 * @param {string} challenge - 随机字符串 * @param {number} nonce - 前端计算出的nonce * @param {number} difficulty - 难度系数, 需要hash前多少位是"0" * @returns {boolean} - 是否满足难度要求 */ function verifyPow(challenge: string, nonce: number, difficulty: number): boolean { // 这里用node:crypto的createHash在Deno里也能用, 同样可在Node.js中复用 const hash = createHash("sha256").update(challenge + nonce).digest("hex"); const success = hash.startsWith("0".repeat(difficulty)); // 后端日志按照统一格式输出 console.log(`[INFO] verifyPow -> challenge="${challenge}", nonce=${nonce}, difficulty=${difficulty}`); console.log(`[INFO] verifyPow -> computedHash="${hash}", success=${success}`); return success; } /** * 运行Deno服务器, 处理前端访问: * - GET / -> 返回HTML_PAGE(前端页面) * - GET /challenge -> 返回challenge和difficulty * - POST /verify -> 接收前端提交的(challenge, nonce, difficulty), 验证是否满足哈希难度 */ serve(async (req) => { const { method } = req; const url = new URL(req.url); // 后端请求日志 console.log(`[INFO] REQUEST -> method=${method}, path=${url.pathname}`); // 当访问根路径时, 返回内置HTML页面 if (url.pathname === "/") { console.log("[INFO] Serving root HTML page"); return new Response(HTML_PAGE, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); } // 前端获取challenge和difficulty if (url.pathname === "/challenge") { console.log("[INFO] 生成新的 challenge 与 difficulty..."); // 生成随机字符串 const challenge = crypto.randomUUID(); // 可以自行调整难度, 这里默认为4 const difficulty = 4; console.log(`[INFO] 下发给前端 challenge="${challenge}", difficulty=${difficulty}`); return new Response( JSON.stringify({ challenge, difficulty }), { headers: { "Content-Type": "application/json" } } ); } // 前端提交验证 if (url.pathname === "/verify" && method === "POST") { try { // 记录后端验证开始时间, 观察耗时 const verifyStartTime = performance.now(); // 解析前端提交的数据 const { challenge, nonce, difficulty } = await req.json(); console.log(`[INFO] 接收到验证请求 -> challenge="${challenge}", nonce=${nonce}, difficulty=${difficulty}`); // 调用verifyPow函数验证 const success = verifyPow(challenge, nonce, difficulty); console.log(`[INFO] 验证结果 -> ${success ? "SUCCESS" : "FAILURE"}`); // 统计后端验证耗时 const verifyEndTime = performance.now(); const costMs = (verifyEndTime - verifyStartTime).toFixed(2); console.log(`[INFO] 本次验证耗时 -> ${costMs} ms`); // 返回结果给前端 return new Response( JSON.stringify({ success }), { headers: { "Content-Type": "application/json" }, status: success ? 200 : 400, }, ); } catch (err) { // 异常处理 console.log(`[ERROR] Exception during verification -> ${err.message}`); return new Response( JSON.stringify({ success: false, error: err.message }), { headers: { "Content-Type": "application/json" }, status: 400, }, ); } } // 其他未匹配的路径返回404 console.log(`[WARN] 404 Not Found -> ${url.pathname}`); return new Response("Not Found", { status: 404 }); });


代码说明

  1. 前端部分 (HTML_PAGE):
  • 使用一个<button>触发流程:
  1. 发起/challenge请求拿到challengedifficulty.
  2. 调用computeProofOfWork执行工作量证明, 不断递增nonce并计算challenge + nonce的哈希, 直到满足hashValue.startsWith("0".repeat(difficulty)).
  3. challenge, nonce, difficulty提交到/verify.
    • 日志函数frontLog(level, message)同时在浏览器控制台和页面上输出日志, 方便调试.
  1. 后端部分:
  • serve方法启动Deno的HTTP服务器.
  • verifyPow函数用node:cryptocreateHash("sha256")来计算哈希, 并校验是否满足难度. 因为是node:crypto, 所以同样可以在Node.js环境下直接用这段逻辑, 只要代码结构兼容ES module/ CommonJS 即可.
  • 路由/challenge随机生成challenge, 设置difficulty, 并返回给前端.
  • 路由/verify接收前端的challenge, nonce, difficulty, 调用verifyPow验证. 验证通过就返回success=true.
  • 访问其他路径则返回404.
  1. 运行环境:
  • Deno 2.1+ (支持node:crypto以及std/http/server.ts).
  • Node.js中若想复用哈希计算逻辑, 只需同样import { createHash } from "node:crypto", 并在Node后端中搭配http或其他框架(Express/Koa)来写路由即可.

这样, 一份代码就能让你在Deno里直接试验工作量证明的原理和效果, 而核心的哈希运算逻辑也能在Node.js里无缝使用.

END

免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。

卡卷网

卡卷网 主页 联系他吧

请记住:卡卷网 Www.Kajuan.Net

欢迎 发表评论:

请填写验证码