验证码能防 CSRF 吗?我这样用对不对?
最近在给登录接口加 CSRF 防护,听说加验证码可以防,但我试了下感觉不太对劲。前端提交表单时带上了用户输入的验证码,后端也校验了,但好像还是可能被 CSRF 攻击?是不是我理解错了?
我现在是这么做的:用户输入用户名、密码和验证码,一起 POST 到 /login,后端验证验证码是否正确。但 CSRF 攻击者根本看不到验证码图片,应该没法伪造请求吧?可同事说这不保险……
fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'test',
password: '123456',
captcha: userInputCaptcha // 用户手动输入的验证码
})
})
先说为什么能防:CSRF攻击的核心是攻击者诱导用户访问一个恶意页面,让浏览器自动发请求到你的服务器。由于同源策略,攻击者的页面是看不到你的验证码图片的,所以他没法把正确的验证码塞到请求里。这样服务器校验验证码时就会失败,攻击就被挡住了。
但问题在于你只保护了登录接口。CSRF攻击通常发生在用户登录之后,比如修改密码、转账、删除数据这些敏感操作。你如果只在登录页用验证码,那其他接口怎么办?总不能每个操作都让用户输验证码吧,用户会疯的。
另外,用验证码防CSRF还有个隐患:验证码必须是一次性的、绑定到用户session的。如果你实现时有什么漏洞(比如验证码存在cookie里、可以重复使用),那防护就形同虚设了。
更标准的做法是用CSRF Token。流程是这样的:用户访问页面时,服务器生成一个随机token存在session里,同时传给前端埋到表单或请求头里。提交请求时带上这个token,服务器校验token和session里的是否匹配。由于攻击者的页面拿不到这个token(同样受同源策略限制),所以伪造的请求会被拒绝。
简单说:验证码能防CSRF,但不是最佳方案。建议登录用验证码防暴力破解,其他敏感操作用CSRF Token。如果你非要只用验证码,那就确保每个请求都生成新验证码、绑定session、校验一次就失效。