一、什么是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')
        # 验证短信验证码...

四、实战中的注意事项

  1. Token存储策略:不要存在Cookie中,建议使用服务器Session或Redis
// 使用Redis存储Token(Spring Boot示例)
@Bean
public CsrfTokenRepository csrfTokenRepository() {
    RedisTokenRepository repository = new RedisTokenRepository();
    repository.setRedisTemplate(redisTemplate);
    return repository;
}
  1. AJAX请求处理
// 前端设置CSRF Token
$.ajaxSetup({
    beforeSend: function(xhr) {
        xhr.setRequestHeader('X-CSRF-TOKEN', getCSRFToken());
    }
});
  1. 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()
    }
}

五、不同场景下的防御选择

  1. 传统Web应用:CSRF Token + SameSite Cookie
  2. 单页应用(SPA):JWT + 自定义请求头
  3. 混合App:设备指纹 + 动态签名
  4. 开放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;
});

六、常见误区与陷阱

  1. GET请求也危险:虽然规范说GET应该只获取数据,但很多开发者会用GET实现修改操作
  2. 过度依赖前端验证:所有前端验证都可以被绕过
  3. 忽略子域名风险:*.example.com下的任意子域名都可能相互影响
  4. Token泄露问题:通过XSS漏洞可能窃取CSRF Token
# 错误的Rails配置示例(不要这样做!)
protect_from_forgery except: [:create]

七、防御体系升级建议

  1. 监控异常请求:统计异常的Referer和缺少Token的请求
  2. 自动化测试:在CI/CD流程中加入CSRF测试用例
  3. 安全头设置
# Nginx配置安全头
add_header X-Frame-Options "DENY";
add_header Content-Security-Policy "frame-ancestors 'none'";
  1. 定期审计:检查所有表单和API端点

八、总结与最佳实践

构建完整的CSRF防御就像给房子装防盗系统:既要有门锁(Token),也要有监控(日志),还要有警报(异常检测)。记住这几个要点:

  1. 关键操作必须使用POST请求
  2. 敏感Cookie设置HttpOnly和SameSite属性
  3. 采用成熟的框架安全机制
  4. 永远不要相信客户端传来的任何数据

最后送大家一个检查清单:

  • [ ] 所有表单包含CSRF Token
  • [ ] AJAX请求携带Token
  • [ ] 关键Cookie标记Secure/HttpOnly
  • [ ] 重要操作需要二次验证
  • [ ] 定期进行安全测试