CSRF token 必须每次页面加载由后端动态生成并绑定 session 和时间戳,文件上传需后端校验 magic bytes 且仅允白名单 MIME 类型,密码字段须用 type="password" 并正确设置 autocomplete,表单 action 和 method 必须硬编码或白名单校验。

CSRF token 必须每次页面加载都刷新
后端生成的 csrf_token 不是静态值,不能复用或缓存。如果用户多次打开同一表单页(比如刷新、返回),旧 token 已失效,但前端若从 localStorage 或 JS 变量里读取旧值,提交必然失败——更糟的是,攻击者可能利用过期 token 的时间窗口发起重放攻击。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 服务端在渲染表单 HTML 时,必须调用一次性生成逻辑(如
crypto.randomBytes(32)),写入 session 并输出到<input type="hidden" name="csrf_token" value="..."> - 禁止前端 JS 自己拼接或存储 token;AJAX 提交前应从 DOM 中实时读取
document.querySelector('input[name="csrf_token"]').value - token 过期时间建议设为 15 分钟,且绑定用户 session ID 和时间戳,服务端校验时需同时比对有效性与时效性
文件上传的 MIME 类型必须由后端读取文件头判定
accept=".pdf,.docx" 和前端 JS 的 File.type 完全不可信。浏览器允许用户手动修改文件扩展名,甚至伪造 type 字段;攻击者上传 shell.php 并改名为 report.pdf,前端毫无察觉。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 后端收到文件后,必须跳过请求头里的
Content-Type,直接读取文件前几百字节(magic bytes)识别真实类型 - Node.js 可用
file-type库,PHP 用finfo_file(),Python 用python-magic - 拒绝一切非白名单类型(如只允许
application/pdf、image/jpeg),并重命名文件为 UUID + 固定后缀,彻底剥离原始文件名
密码字段必须用 type="password" + autocomplete 显式声明
用 type="text" 配 CSS 遮盖字符,或仅靠 autocomplete="off",既不阻止浏览器自动填充,也不触发密码管理器的安全行为。更危险的是,部分浏览器会把 type="text" 字段的输入内容缓存在内存或日志中。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 登录页密码字段:使用
<input type="password" name="password" autocomplete="current-password"> - 注册/改密页密码字段:用
autocomplete="new-password",明确告知浏览器这是新凭据 - 非密码字段(如邮箱)若不想被 autofill 错填,优先用随机
name(如name="email_7f3a"),而非依赖已被现代浏览器忽略的autocomplete="off"
表单 action 和 method 必须硬编码或白名单校验
如果 action 来自 URL 参数或用户输入(如 ?next=/admin/delete),攻击者可构造链接诱导用户提交到任意地址;method 若未显式声明,默认是 GET,导致敏感参数暴露在日志、代理、浏览器历史中。
实操建议:
立即学习“前端免费学习笔记(深入)”;
-
action值必须硬编码,如action="/api/login";若需动态,只允许白名单内路径(如/api/login、/api/register) - 始终显式写
method="POST",服务端也必须校验请求方法,对/api/login拒绝所有非 POST 请求 - 避免在 JS 中拼接
form.action = '/api/' + userAction—— 这等于开放重定向入口
最容易被忽略的其实是 token 生命周期和文件头校验这两环:前者常被当成“加了就行”,后者常被当成“前端拦住就安全了”。真实攻击往往就卡在这两个看似细小的断点上。



















