一、什么是 CSRF 攻击

CSRF 即跨站请求伪造(Cross - Site Request Forgery),简单来说,就是攻击者通过诱导用户在已登录的网站上执行非本意的操作。想象一下,你在银行网站登录后,没有退出,然后打开了一个恶意网站。这个恶意网站偷偷地向银行网站发送了一个转账请求,由于你在银行网站是已登录状态,银行网站会认为这个请求是你本人发起的,从而执行转账操作,这就是 CSRF 攻击的典型场景。

示例(使用 Java 和 Spring Boot 技术栈)

以下是一个简单的 Java Spring Boot 示例,模拟一个存在 CSRF 风险的表单提交:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class CsrfExampleController {

    // 模拟一个账户余额
    private int accountBalance = 1000;

    // 显示转账页面
    @GetMapping("/transfer")
    public String showTransferPage() {
        return "transfer";
    }

    // 处理转账请求
    @PostMapping("/transfer")
    @ResponseBody
    public String transfer(@RequestParam("amount") int amount, @RequestParam("to") String to) {
        if (amount <= accountBalance) {
            accountBalance -= amount;
            return "Transfer successful! New balance: " + accountBalance;
        } else {
            return "Insufficient funds";
        }
    }
}

注释:上述代码中,showTransferPage 方法用于显示转账页面,transfer 方法用于处理转账请求。但这个代码没有对 CSRF 进行防护,存在安全风险。

二、CSRF 攻击的应用场景

金融领域

在金融网站中,用户登录后可能会进行转账、支付等操作。如果网站没有有效的 CSRF 防护,攻击者可以诱导用户在已登录的情况下访问恶意网站,从而执行非用户本意的转账或支付操作,给用户带来经济损失。

社交网络

在社交网络中,攻击者可以利用 CSRF 攻击让用户在不知情的情况下发布恶意内容、关注特定账号等。例如,攻击者可以构造一个恶意链接,当用户点击该链接时,会自动在用户的社交账号上发布一条包含恶意信息的动态。

企业内部系统

企业内部的办公系统、财务管理系统等也可能成为 CSRF 攻击的目标。攻击者可以通过诱导企业员工在已登录的情况下访问恶意网站,执行如修改员工信息、删除重要数据等操作,影响企业的正常运营。

三、CSRF 攻击的技术优缺点

优点(对于攻击者而言)

  • 隐蔽性强:CSRF 攻击不需要用户手动输入敏感信息,而是利用用户在其他网站的已登录状态,用户很难察觉自己正在遭受攻击。例如,用户在浏览网页时,可能只是随意点击了一个链接,就不知不觉地执行了恶意操作。
  • 实施成本低:攻击者只需要构造一个包含恶意请求的链接或页面,诱导用户访问即可,不需要复杂的技术手段。

缺点(对于攻击者而言)

  • 依赖用户登录状态:CSRF 攻击必须在用户已经登录目标网站的情况下才能生效。如果用户没有登录,攻击就无法成功。
  • 受同源策略限制:虽然 CSRF 攻击可以跨站,但仍然受到浏览器同源策略的一定限制。某些情况下,浏览器会阻止跨站请求,从而降低攻击的成功率。

四、CSRF 攻击防护实践

使用 CSRF 令牌

CSRF 令牌是一种常用的防护 CSRF 攻击的方法。服务器在生成页面时,会为每个用户生成一个唯一的令牌,并将其嵌入到页面中。当用户提交请求时,服务器会验证请求中携带的令牌是否与服务器端存储的令牌一致。如果一致,则认为请求是合法的;否则,拒绝请求。

示例(Java 和 Spring Boot 技术栈)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
           .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
           .and()
           .authorizeRequests()
           .anyRequest().authenticated()
           .and()
           .formLogin();
        return http.build();
    }
}

注释:上述代码使用 Spring Security 框架开启 CSRF 防护,CookieCsrfTokenRepository.withHttpOnlyFalse() 用于生成 CSRF 令牌并存储在 cookie 中。在前端页面中,需要将令牌添加到表单或请求头中。

验证请求来源

服务器可以通过验证请求的来源(如 Referer 头)来判断请求是否合法。如果请求的来源不是合法的网站,则拒绝请求。

示例(Java 和 Spring Boot 技术栈)

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class RefererFilter extends OncePerRequestFilter {

    private static final String ALLOWED_ORIGIN = "https://example.com";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String referer = request.getHeader("Referer");
        if (referer != null &&!referer.startsWith(ALLOWED_ORIGIN)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid referer");
            return;
        }
        filterChain.doFilter(request, response);
    }
}

注释:上述代码实现了一个过滤器,用于验证请求的 Referer 头。如果 Referer 头不是来自允许的域名,则返回 403 错误。

同源检查

浏览器的同源策略可以在一定程度上防止 CSRF 攻击。服务器可以设置响应头,限制页面只能在同源的情况下加载。

示例(Java 和 Spring Boot 技术栈)

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SameOriginFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setHeader("Content-Security-Policy", "default-src'self'");
        filterChain.doFilter(request, response);
    }
}

注释:上述代码实现了一个过滤器,设置了 Content - Security - Policy 响应头,限制页面只能加载来自同源的资源。

五、注意事项

令牌的安全性

CSRF 令牌必须是唯一且随机的,并且要妥善存储。如果令牌被泄露,攻击者可以利用该令牌进行 CSRF 攻击。

跨域问题

在使用验证请求来源和同源检查时,需要注意跨域问题。如果网站需要支持跨域请求,需要进行额外的配置。

兼容性问题

不同的浏览器对 CSRF 防护机制的支持可能存在差异,需要进行充分的测试,确保在各种浏览器中都能正常工作。

六、文章总结

CSRF 攻击是一种常见且危险的网络安全威胁,它可以利用用户的已登录状态执行非本意的操作。为了防护 CSRF 攻击,可以采用多种方法,如使用 CSRF 令牌、验证请求来源、同源检查等。在实施防护措施时,需要注意令牌的安全性、跨域问题和兼容性问题。通过合理的防护措施,可以有效地降低 CSRF 攻击的风险,保障网站和用户的安全。