大家好😁。
上个月 Code Review,我拦下了一个新人的代码。
他写了一个转账功能,前端做了极其严密的校验:
disabled 的。他自信满满地跟我说:老大,放心吧,我前端卡得死死的,用户绝对传不了非法数据。
我笑了笑🤣,没看他的后端代码,直接打开终端,敲了一行命令。
<"ear.lgbaobongda.com"><"goat.lgbaobongda.com"><"hat.lgbaobongda.com"><"ink.lgbaobongda.com">
<"jacket.lgbaobongda.com"><"lion.lgbaobongda.com"><"nose.lgbaobongda.com"><"owl.lgbaobongda.com">
0.5 秒后,他的数据库里多了一笔“-10000”的转账记录,余额瞬间暴涨!
他看着屏幕,目瞪口呆:这……你是怎么做到的?我按钮明明置灰了啊!
今天,我就来揭秘这个所有后端(和全栈)工程师必须铭记的第一铁律:
前端验证,在黑客眼里,只是个小case🤔。
假设我们有一个购物网站,前端有一个简单的购买表单。
<"pig.lgbaobongda.com"><"ring.lgbaobongda.com"><"snake.lgbaobongda.com"><"table.lgbaobongda.com">
<"uncle.lgbaobongda.com"><"watch.lgbaobongda.com"><"yarn.lgbaobongda.com"><"bag.lgbaobongda.com">
前端逻辑(看似完美):
// Front-end code
function
submitOrder(
price, quantity) {
// 1. 校验价格不能被篡改
if (price !==
999) {
alert(
"价格异常!");
return;
}
// 2. 校验数量必须为正数
if (quantity <=
0) {
alert(
"数量必须大于0!");
return;
}
// 发送请求
api.
post(
'/buy', { price, quantity });
}
你看,用户在浏览器里确实没法作恶。他改不了价格,也填不了负数。
但是黑客,从来不用浏览器点你的按钮。
第一步:打开DevTools Network 面板,正常点一次购买按钮。捕获到了这个请求。
第二步:请求上右键 -> 复制 -> cURL 格式复制。
<"cake.lgbaobongda.com"><"duck.lgbaobongda.com"><"frog.lgbaobongda.com"><"grape.lgbaobongda.com">
<"horse.lgbaobongda.com"><"insect.lgbaobongda.com"><"jelly.lgbaobongda.com"><"king.lgbaobongda.com">
这一步,我已经拿到了你发送请求的所有密钥:URL、Headers、Cookies、以及那个看似合法的 Data。
第三步:打开终端(Terminal),粘贴刚才复制的命令。但是,我并没有直接回车。
我修改了
--data-raw 里的参数:
"price": 999 改成了
"price": 0.01"quantity": 1 改成了
"quantity": -100
# 经过魔改后的命令
curl
'http://localhost:3000/user/buy' \
-H
'Cookie: session_id=...' \
-H
'Content-Type: application/json' \
--data-raw
'{"price": 0.01, "quantity": 10}' \
--compressed
回车!
服务器返回:
{ "status": "success", "msg": ok!" }
<"lamp.lgbaobongda.com"><"nest.lgbaobongda.com"><"pencil.lgbaobongda.com"><"quilt.lgbaobongda.com">
<"river.lgbaobongda.com"><"shoe.lgbaobongda.com"><"tiger.lgbaobongda.com"><"whale.lgbaobongda.com">
恭喜你,你的前端验证毫发无损,但你的数据库已经被我击穿了。 我用 1 分钱买了 10 个商品,或者通过负数数量,反向刷了库存。
很多新人最大的误区,就是认为 用户只能通过我的 UI 来访问我的服务器。
错!大错特错!
Web 的本质是 HTTP 协议。
HTTP 协议是无状态的、公开的。任何能够发送 HTTP 请求的客户端,都是你的用户。
cURL 是客户端。requests 脚本也是客户端。http 脚本也是客户端前端代码运行在用户的电脑上。
这意味着,用户拥有对前端代码的 绝对控制权。
所以, 前端验证的唯一作用,是提升用户体验 (比如提示用户格式不对😂),而不是提供安全性😖。
既然前端不可信,后端(或 BFF 层)就必须假设 所有发过来的数据都是有毒的。
1. 永远不要相信 Payload 里的关键数据
前端只传
productId。后端拿到 ID 后,
去数据库里查这个商品到底多少钱。永远以数据库为准。
2. 使用 Schema 校验库(Zod / Joi / class-validator)
不要在 Controller 里写一堆 if (req.body.age < 0)。
使用专业的 Schema 校验库,定义好数据的规则。
TypeScript代码👇:
// 使用 Zod 定义后端校验规则
const
OrderSchema = z.
object({
productId: z.
string(),
// 强制要求 quantity 必须是正整数,拦截 -100 这种攻击
quantity: z.
number().
int().
positive(),
// 注意:这里根本不接收 price 字段,防止被注入
});
// 如果校验失败,直接抛出 400 错误,逻辑根本进不去
const data =
OrderSchema.
parse(req.
body);
3. 权限与状态校验
不要只看数据格式对不对,还要看人对不对。
你以为校验了数据就安全了?
如果我拦截了你一次领优惠券的请求,虽然我改不了数据,但我可以用
cURL
连续运行 1000 次这个命令。
# 一个简单的循环,瞬间刷爆你的接口
for i
in {1..1000};
do curl ... ;
done
如果你的后端没有做 幂等性(Idempotency)校验或频率限制(Rate Limiting) ,那我瞬间就能领走 1000 张优惠券。
防御手段👇:
对于前端安全, 所有的输入都是可疑的🤔
作为全栈或后端开发者,当你写 API 时,请忘掉你那个漂亮的前端界面。
你的脑海里应该只有一幅画面:
屏幕对面,不是一个点鼠标的用户,而是一个正在敲 cURL 命令的黑客。
只有这样,你的代码才算真正安全了😒。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/53956/viewspace-3105559/ ,如需转载,请注明出处,否则将追究法律责任。