关注: CodingTechWork

引言

  Cross-Site Request Forgery(CSRF)是一种冒充用户执行非预期操作的攻击方式,它在OWASP Top 10中曾长期占有一席之地。本文将深入剖析CSRF,并通过Java代码示例,展示如何构建坚固的CSRF防线。

CSRF介绍

  CSRF(跨站请求伪造),也被称为“One-Click Attack”或“Session Riding”。它是一种诱骗已认证用户在不知情的情况下,运行一个恶意Web应用程序操作的攻击。
  核心危害:攻击者并不能直接窃取你的Cookie或会话数据,而是利用你的浏览器对目标网站的已认证状态,以你的名义执行恶意操作,如修改密码、转账、发布内容等。

CSRF攻击类似的流程

  • 你已登录网上银行(相当于在银行大厅办理业务)。
  • 攻击者给你一个精心设计的链接(相当于递给你一张伪造的“转账授权书”)。
  • 你点击了链接,浏览器自动携带你的登录凭证(Cookie)向银行发起转账请求(相当于你在不知情的情况下签署了授权书)。
  • 银行看到是你的凭证,便执行了操作。

CSRF攻击的原理与产生条件

核心原理

  利用浏览器的同源策略不会阻止向目标网站发送请求的机制,并自动携带用户的认证信息(如Session Cookie)。

必要条件

  1. 用户已登录目标网站(A站),并且会话(Session)未过期。
  2. 用户访问了恶意网站(B站),该网站包含一个自动向A站发起请求的代码。
  3. 目标网站(A站)完全依赖Cookie(通常是Session Cookie)来认证用户身份,没有其他机制来验证请求的意图。
  4. 请求是可预测的,攻击者能构造出执行特定操作所需的请求参数。

一个CSRF攻击是如何发生的?

假设有一个Java Web应用,提供一个修改用户邮箱的GET接口(注意:使用GET进行状态变更操作本身就是不安全的)。

1. 易受攻击的Java Servlet

// 危险的Servlet:使用GET方法执行敏感操作,且无CSRF防护
@WebServlet("/changeEmail")
public class ChangeEmailServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 从Session中验证用户登录状态
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("user") != null) {
            // 2. 用户已登录,执行修改邮箱操作
            String newEmail = request.getParameter("newEmail");
            User user = (User) session.getAttribute("user");
            userService.updateEmail(user.getId(), newEmail); 
            response.getWriter().write("Email updated successfully!");
        } else {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

2. 攻击者构造的恶意页面(malicious-site.com)

<!DOCTYPE html>
<html>
<head>
    <title>看起来无害的页面</title>
</head>
<body>
    <h1>来看一张有趣的图片!</h1>
    <!-- 利用img标签的src属性自动发起GET请求 -->
    <img src="http://your-java-app.com/changeEmail?newEmail=hacker@evil.com" 
         width="0" height="0" alt="Fake Image" />
    <p>(实际上,页面在后台悄悄修改了你在“your-java-app.com”的邮箱)</p>

    <script>
        // 或者使用JavaScript自动提交一个隐藏的表单(对POST请求同样有效)
        setTimeout(function() {
            document.getElementById('csrf-form').submit();
        }, 100);
    </script>

    <!-- 隐藏的表单,用于模拟POST请求 -->
    <form id="csrf-form" action="http://your-java-app.com/changeEmail" method="POST" style="display:none;">
        <input type="hidden" name="newEmail" value="hacker@evil.com" />
    </form>
</body>
</html>

攻击流程

  1. 用户登录了 http://your-java-app.com,Session有效。
  2. 用户随后访问了 http://malicious-site.com
  3. 恶意页面中的 <img> 标签或JavaScript会自动向 your-java-app.com 发起请求(携带用户的Session Cookie)。
  4. 你的Java应用收到请求,验证Session有效,于是执行了修改邮箱的操作。

如何在Java世界中防御CSRF?

防御CSRF的核心思想是:增加一个攻击者无法预测且无法伪造的额外验证凭证

1. 使用Anti-CSRF Token(同步器令牌模式)

原理:在用户会话中存储一个随机、不可预测的Token(令牌)。在渲染表单时,将此Token作为一个隐藏字段放入表单。当表单提交时,服务器验证提交的Token是否与会话中存储的Token一致。

2. 使用Spring Security

如果你使用Spring框架,Spring Security默认就提供了强大且开箱即用的CSRF防护

  • 默认行为:Spring Security会为每个Session生成一个CSRF Token,并期望在所有非GET、HEAD、TRACE、OPTIONS的请求(如POST, PUT, PATCH, DELETE)中携带一个名为_csrf的参数或X-CSRF-TOKEN头。
  • 与Thymeleaf集成:在Thymeleaf模板中,只需在表单中添加th:action,Thymeleaf会自动帮你插入CSRF Token隐藏域。
    <form th:action="@{/changeEmail}" method="post">
        <input type="email" name="newEmail">
        <button type="submit">更新邮箱</button>
        <!-- Thymeleaf会自动生成:<input type="hidden" name="_csrf" value="..."> -->
    </form>
    
  • 自定义与禁用:如果需要为某些接口(如API)禁用CSRF,可以在配置中操作
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                .authorizeHttpRequests(authz -> authz
                    .anyRequest().authenticated()
                )
                .csrf(csrf -> csrf
                    // 忽略某些API路径,例如/webhook(风险自担)
                    // .ignoringRequestMatchers("/webhook/**")
                );
            return http.build();
        }
    }
    

3. 验证SameSite Cookie

为Cookie设置SameSite属性可以作为一种有效的深度防御措施。

  • SameSite=Strict:最严格,完全禁止第三方Cookie。
  • SameSite=Lax:(默认值)在大多数跨站导航(如从外站链接点击过来)时发送Cookie,但不会在跨站的POST请求或图像加载中发送。
  • SameSite=None:允许跨站发送,但必须同时设置Secure属性(HTTPS)。

web.xml或Servlet中配置:

<session-config>
    <cookie-config>
        <http-only>true</http-only>
        <!-- 仅HTTPS -->
        <secure>true</secure> 
         <!-- 或 strict -->
        <same-site>lax</same-site>
    </cookie-config>
</session-config>
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐