概念定义
“用请求键值袋填充对象”很顺手也很危险:客户端控制键,模型有许多可设属性,交集里就有业务从没想暴露的字段。GitHub 2012 的公钥更新端点接受 admin 标志,是教科书案例。
工作机制
框架把 JSON 键映射到 setter 或列。防御要倒过来:每个端点声明精确接受的字段——strong parameters、显式 DTO、模式校验——未知键被拒绝或忽略而不是合并;所有权与租户字段一律从认证主体推导,绝不信请求体。
使用场景
审计每个创建与更新处理器,尤其是共享 DTO 的管理相邻流程。契约测试应提交带毒的额外键,并断言响应与持久化实体都不含它们。
常见误区
输出端隐藏字段只管响应,输入绑定才是攻击面;ORM 默认绑定全部列,防它必须显式 permit。嵌套对象会把字段藏到比白名单更深一层;管理员与自助服务共用一个 DTO 终会并集膨胀,应拆分。审计日志记录被拒键,可作为探测信号。
代码示例
import express from 'express';
import jwt from 'jsonwebtoken';
function requireAuthMiddleware(req, res, next) {
const header = req.headers.authorization ?? '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
if (!token) return res.status(401).json({ error: 'token required' });
try {
req.claims = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(403).json({ error: 'invalid token' });
}
}
function requireRoleMiddleware(...allowed) {
return (req, res, next) => {
const role = req.claims?.role;
if (!role || !allowed.includes(role)) {
return res.status(403).json({ error: 'role ' + role + ' not permitted' });
}
next();
};
}
const app = express();
app.delete('/invoices/:id',
requireAuthMiddleware,
requireRoleMiddleware('admin', 'finance'),
(req, res) => res.json({ deleted: req.params.id }),
);
app.get('/profile',
requireAuthMiddleware,
(req, res) => res.json({ userId: req.claims.sub }),
);
app.listen(3000);
package main
import (
"crypto/sha256"
"crypto/subtle"
"fmt"
)
func ConstantTimeEqual(a, b string) bool {
left := sha256.Sum256([]byte(a))
right := sha256.Sum256([]byte(b))
return subtle.ConstantTimeCompare(left[:], right[:]) == 1
}
func main() {
secret := "s3cr3t-token"
fmt.Println(ConstantTimeEqual(secret, "s3cr3t-token"))
fmt.Println(ConstantTimeEqual(secret, "s3cr3t-tokeX"))
fmt.Println(ConstantTimeEqual(secret, "short"))
}