在 Java Web 应用程序上生成并验证 CSRF 令牌

mas*_*r17 1 java csrf spring-security spring-boot csrf-token

我要求实施 CSRF 来防止对 java 服务器应用程序的攻击。这是一个提供大量 Web REST API 服务的应用程序。\n我查看了许多指南并在堆栈上进行了搜索,但仍然有一些顾虑。\n我知道对于 GET 请求来说是不需要的。

\n

所以,如果我错了,请纠正我。

\n
    \n
  1. 浏览器第一次发送请求以获取会话的 CSRF 令牌,大约 30 分钟
  2. \n
  3. 然后浏览器将此令牌放入脚本中并发送给后端。
  4. \n
\n

这是我的过滤器。

\n
    public class ValidateCSRFToken implements Filter {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateCSRFToken.class);\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {\n        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;\n        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;\n        // Spring put the CSRF token in session attribute "_csrf"\n        CsrfToken csrfToken = (CsrfToken) httpServletRequest.getAttribute("_csrf");\n\n        // Send the cookie only if the token has changed\n        String actualToken = httpServletRequest.getHeader("X-CSRF-TOKEN");\n\n        if (!StringUtils.isEmpty(csrfToken)) {\n            LOGGER.info("CSRF token " + csrfToken.getToken());\n        }\n\n        if (!StringUtils.isEmpty(actualToken)) {\n            LOGGER.info("X-CSRF-TOKEN " + actualToken);\n        }\n\n        if (actualToken == null || !actualToken.equals(csrfToken.getToken())) {\n            String pCookieName = "CSRF-TOKEN";\n            Cookie cookie = new Cookie(pCookieName, csrfToken.getToken());\n            cookie.setMaxAge(-1);\n            cookie.setHttpOnly(false);\n            cookie.setPath("/");\n            httpServletResponse.addCookie(cookie);\n        }\n\n        filterChain.doFilter(httpServletRequest, httpServletResponse);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的网络安全:

\n
  //Start chain for restricting access.\n    http.addFilterAfter(new ValidateCSRFToken(), CsrfFilter.class)\n            .authorizeRequests()\n            //The following paths\xe2\x80\xa6\n            .antMatchers("**")\n            // \xe2\x80\xa6are accessible to all users (authenticated or not).\n            .permitAll()\n            .antMatchers( "/actuator/**").permitAll()\n            // All remaining paths\xe2\x80\xa6\n            .anyRequest()\n            // ...require user to at least be authenticated\n            .authenticated()\n            .and()// And if a user needs to be authenticated...\n            .formLogin()\n            // ...redirect them to /username`\n            .loginPage("/username")\n            .and()\n            .logout().clearAuthentication(true).invalidateHttpSession(true).deleteCookies("JSESSIONID")\n            .and()\n            // If user isn't authorised to access a path...\n            .exceptionHandling()\n            // ...redirect them to /403\n            .accessDeniedPage("/403")\n            .and()\n            .cors()\n            .and()\n            .csrf();\n
Run Code Online (Sandbox Code Playgroud)\n

// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());;

\n

所以,问题是:

\n
    \n
  1. 如何验证它而不通过过滤器链?
  2. \n
  3. 当浏览器第一次发出请求时,我们如何知道这个请求是属于浏览器的,而不是攻击者的?
  4. \n
\n

rzw*_*oot 5

据我所知,对于 GET 请求来说是不需要的。

其目的是确保用户所做的任何有副作用的事情实际上都是有意的交互。现在,GET请求应该完全没有副作用,但如果你搞砸了,并且某些 GET 请求确实有副作用,那么你要么需要修复它,要么如果你不能,那么你的 GET 请求也需要 CSRF 保护。这有点棘手,将令牌发送到 URL 中并不完全是一个好主意(这就是会发生的情况)。

例如,如果https://yourserver.com/commands/deletePost?p=18您以管理员身份登录,则只需单击即可从数据库中删除 unid 18 的文章,我可以使用以下命令创建一个网站:

<a href="https://yourserver.com/commands/deletePost?p=18">Click to see cute kittens!</a>
Run Code Online (Sandbox Code Playgroud)

并发布它。然后我就等你点击它,瞧。帖子没了。这就是 CSRF 的意义所在。

因此,请浏览整个代码库。其中是否存在任何 GET 请求导致状态更改的地方(创建或擦除的文件、数据库 INSERT 或 UPDATE 或 DELETE 命令、用于导致状态更改的 API,例如在 twitter 上发布内容或诸如此类的事情)。修复该代码并确保它们根本不允许这样做并且仅适用于 POST/PUT。

然后将 CSRF 添加到所有这些 POST/PUT(或者,更好的是,仅添加到所有 POST;考虑任何没有 CSRF 令牌的 POST,就好像用户未登录一样)。

请注意,许多 Web 框架套件已经具备 CSRF 保护系统,包括 Spring Security。