一、什么是CSRF攻击?
想象一下这样的场景:你正在网上银行转账,突然收到一封"中奖"邮件,顺手点了个链接。第二天发现账户里少了钱——这可能就是遭遇了CSRF(跨站请求伪造)攻击。简单来说,就是黑客利用你已经登录的状态,偷偷帮你"点按钮"。
这种攻击之所以危险,是因为它完全不需要窃取你的密码。就像有人趁你睡着时,拿着你的手指去按指纹锁。目前OWASP Top 10中,CSRF仍然位列安全威胁前列。
二、CSRF攻击的典型套路
来看个具体案例。假设有个简陋的银行转账页面(使用PHP+MySQL技术栈):
// 转账处理代码(存在CSRF漏洞)
if(isset($_POST['amount']) && isset($_POST['to_account'])){
$amount = $_POST['amount'];
$to_account = $_POST['to_account'];
// 这里没有验证请求来源
$sql = "UPDATE accounts SET balance = balance - $amount WHERE user_id = {$_SESSION['user_id']}";
mysqli_query($conn, $sql);
$sql = "UPDATE accounts SET balance = balance + $amount WHERE account_num = '$to_account'";
mysqli_query($conn, $sql);
echo "转账成功!";
}
黑客只需要在自己的网站上放这样一个钓鱼页面:
<!-- 恶意网站上的陷阱 -->
<body onload="document.forms[0].submit()">
<form action="http://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="to_account" value="hacker123">
</form>
</body>
当已登录银行的用户访问这个页面时,转账请求就会自动发出。整个过程用户完全不知情,就像被远程操控的提线木偶。
三、防御CSRF的五大法宝
1. 验证请求来源(Referer检查)
// PHP实现Referer检查
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if(!str_contains($referer, 'bank.com')){
die("非法请求来源!");
}
不过这个方法有漏洞:某些浏览器可能不发送Referer,或者黑客通过特殊手段伪造Referer头。
2. 使用CSRF Token(推荐方案)
就像给每个表单发个"动态口令",这里用Java Spring Security演示:
// 服务端生成Token
@Controller
public class CsrfController {
@GetMapping("/form")
public String showForm(Model model) {
// 自动生成并存储Token
model.addAttribute("_csrf", request.getAttribute("_csrf"));
return "transfer-form";
}
}
// 前端表单
<form action="/transfer" method="post">
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
<!-- 其他表单字段 -->
</form>
// 服务端验证
@PostMapping("/transfer")
public String transfer(@RequestParam("_csrf") String token) {
if(!csrfTokenRepository.loadToken(token).equals(token)){
throw new CsrfException("Token验证失败");
}
// 处理转账...
}
3. 双重Cookie验证
// 前端设置自定义Cookie
document.cookie = "X-CSRF-TOKEN=" + randomString();
// 后端验证(Node.js示例)
app.post('/transfer', (req, res) => {
const token = req.cookies['X-CSRF-TOKEN'];
if(!token || token !== req.body.csrfToken){
return res.status(403).send('CSRF验证失败');
}
// 处理业务逻辑...
});
4. SameSite Cookie属性
现代浏览器支持的新特性:
// 设置PHP会话Cookie
session_set_cookie_params([
'samesite' => 'Strict',
'secure' => true,
'httponly' => true
]);
这个方案就像给Cookie加了"区域锁",但要注意兼容性问题。
5. 关键操作二次验证
对于转账等敏感操作,强制要求:
# Django示例
def transfer_view(request):
if request.method == 'POST':
if not request.POST.get('sms_code'):
return render(request, 'verify_sms.html')
# 验证短信验证码...
四、实战中的注意事项
- Token存储策略:不要存在Cookie中,建议使用服务器Session或Redis
// 使用Redis存储Token(Spring Boot示例)
@Bean
public CsrfTokenRepository csrfTokenRepository() {
RedisTokenRepository repository = new RedisTokenRepository();
repository.setRedisTemplate(redisTemplate);
return repository;
}
- AJAX请求处理:
// 前端设置CSRF Token
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-TOKEN', getCSRFToken());
}
});
- API防护方案:
// Golang的Gin框架中间件
func CsrfMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "POST" {
token := c.GetHeader("X-CSRF-Token")
if !isValidToken(token) {
c.AbortWithStatus(403)
return
}
}
c.Next()
}
}
五、不同场景下的防御选择
- 传统Web应用:CSRF Token + SameSite Cookie
- 单页应用(SPA):JWT + 自定义请求头
- 混合App:设备指纹 + 动态签名
- 开放API:OAuth2.0 + 签名验证
比如React应用可以这样集成:
// 在React中管理CSRF Token
import axios from 'axios';
axios.interceptors.request.use(config => {
config.headers['X-CSRF-Token'] = localStorage.getItem('csrfToken');
return config;
});
六、常见误区与陷阱
- GET请求也危险:虽然规范说GET应该只获取数据,但很多开发者会用GET实现修改操作
- 过度依赖前端验证:所有前端验证都可以被绕过
- 忽略子域名风险:*.example.com下的任意子域名都可能相互影响
- Token泄露问题:通过XSS漏洞可能窃取CSRF Token
# 错误的Rails配置示例(不要这样做!)
protect_from_forgery except: [:create]
七、防御体系升级建议
- 监控异常请求:统计异常的Referer和缺少Token的请求
- 自动化测试:在CI/CD流程中加入CSRF测试用例
- 安全头设置:
# Nginx配置安全头
add_header X-Frame-Options "DENY";
add_header Content-Security-Policy "frame-ancestors 'none'";
- 定期审计:检查所有表单和API端点
八、总结与最佳实践
构建完整的CSRF防御就像给房子装防盗系统:既要有门锁(Token),也要有监控(日志),还要有警报(异常检测)。记住这几个要点:
- 关键操作必须使用POST请求
- 敏感Cookie设置HttpOnly和SameSite属性
- 采用成熟的框架安全机制
- 永远不要相信客户端传来的任何数据
最后送大家一个检查清单:
- [ ] 所有表单包含CSRF Token
- [ ] AJAX请求携带Token
- [ ] 关键Cookie标记Secure/HttpOnly
- [ ] 重要操作需要二次验证
- [ ] 定期进行安全测试
评论