大家在做登录功能时,一般怎么做暴力破解防护?
作者:卡卷网发布时间:2025-01-19 19:13浏览数量:307次评论数量:0次
防爆破有一个统一的方法来对抗, 那就是: 工作量证明(Proof of Work).
工作量证明并不复杂: 发起请求前让客户端先算一个十分烧CPU的值-比如说我要找出某个哈希值, 它的前六位必须全是0.
很多人或许听过它在区块链上的应用, 比如比特币挖矿. 其实, 工作量证明的核心思想也能用来对付各种"爆破式攻击".
客户端在发请求前必须先做一段计算, 而服务端只要简单验证就行.
这相当于是说:"老兄, 你想进来可以, 但得先把这一大堆数字给我算完, 再告诉我结果." 而这个计算过程对用户来说, 要消耗CPU, 时间, 电量, 算下来就有了成本. 然后服务端验证却很快, 只需要轻轻计算一次哈希就能知道你算得对不对.
这样一来, 正常用户虽然多花点时间, 几百毫秒, 但还能接受; 可攻击者要是一口气想发几百万次的请求, 攻击者就得老老实实把这一大堆计算都做了-成本瞬间飙升.
工作量证明的优点:
- 验证成本低: 服务端只算一次哈希就能验证. 约耗费1ms即可.
- 攻击成本高: 攻击者想发海量请求, 计算量会成倍增加. 单次请求花费500ms计算,百万次请求几乎就是难以承担的计算成本了.
- 调节灵活: 修改难度, 即可控制一次计算的耗时.
当然, 它也不是万能的. 如果有人有钱又肯烧电, 还是能硬扛. 因此, 实际应用时, 通常还会搭配限速, IP封禁, 验证码, 多因素认证等其他策略.
总之, 工作量证明很适合那些容易被批量尝试的接口. 如果你遇到暴力轰炸或穷举攻击, 不妨试试这个方法. 虽然用户会多花些时间, 但能有效降低爆破的成功率. 毕竟, 算点哈希, 胜过被无限次轰炸.
下面是一份带有注释的单文件示例代码, 展示了如何在Deno环境下使用工作量证明(Proof of Work)来对抗爆破式攻击. 其中使用了node:crypto
来计算SHA-256, 这些哈希计算逻辑也可以在Node.js中直接复用.
使用方式:
- 将这份代码保存为
server.ts
. - 在终端运行: deno run --allow-net server.ts
- 打开浏览器访问
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 });
});
代码说明
- 前端部分 (HTML_PAGE):
- 使用一个
<button>
触发流程:
- 发起
/challenge
请求拿到challenge
和difficulty
. - 调用
computeProofOfWork
执行工作量证明, 不断递增nonce
并计算challenge + nonce
的哈希, 直到满足hashValue.startsWith("0".repeat(difficulty))
. - 将
challenge, nonce, difficulty
提交到/verify
.
- 日志函数
frontLog(level, message)
同时在浏览器控制台和页面上输出日志, 方便调试.
- 后端部分:
serve
方法启动Deno的HTTP服务器.verifyPow
函数用node:crypto
的createHash("sha256")
来计算哈希, 并校验是否满足难度. 因为是node:crypto
, 所以同样可以在Node.js环境下直接用这段逻辑, 只要代码结构兼容ES module/ CommonJS 即可.- 路由
/challenge
随机生成challenge
, 设置difficulty
, 并返回给前端. - 路由
/verify
接收前端的challenge, nonce, difficulty
, 调用verifyPow
验证. 验证通过就返回success=true
. - 访问其他路径则返回404.
- 运行环境:
- Deno 2.1+ (支持
node:crypto
以及std/http/server.ts
). - Node.js中若想复用哈希计算逻辑, 只需同样
import { createHash } from "node:crypto"
, 并在Node后端中搭配http
或其他框架(Express/Koa)来写路由即可.
这样, 一份代码就能让你在Deno里直接试验工作量证明的原理和效果, 而核心的哈希运算逻辑也能在Node.js里无缝使用.
免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。
- 上一篇:你见过最脑残的设计是什么?
- 下一篇:有没有一段代码,让你为人类的智慧击节叫好?
相关推荐

你 发表评论:
欢迎