Spring MVC 中的 CSRF(跨站请求伪造)保护

9
我对Spring中的CSRF(跨站请求伪造)保护感到有些困惑。我有我的JSP、控制器和一个Web服务。我想要做的是在Web服务级别验证令牌,如果令牌匹配,则运行Web服务(在我的情况下执行数据库插入操作)。 JSP文件
    <form:input type="text" class="form-control" path="mName" />

    <input type="hidden" name="${_csrf.parameterName}"
        value="${_csrf.token}" />

    <div class="form-action">
        <input type="submit" value="Save" class="btn btn-primary" />
    </div>
</form:form>

我已经插入了隐藏标签。现在我应该怎么验证这个令牌?我有点迷失了。
在控制器类中,我从表单中获取值到一个对象中,并调用web服务保存数据。
@RequestMapping(method = RequestMethod.POST)
  public String processForm(@ModelAttribute(value = "userForm") @Valid UserForm userForm, BindingResult result, ModelMap model) {      

   //call the web service
  }

2
基于这个标签是spring-security,并且您正在使用_csrf属性,可以安全地假设您正在使用Spring Security。如果是这样,您不需要手动验证CSRF,Spring Security会为您完成此操作。如果这对您来说不够,请问为什么不起作用?最后,如果您确实需要自己验证它,可以使用HttpSessionCsrfTokenRepository的实例来获取预期的令牌。 - Rob Winch
@RobWinch 是的,我正在使用Spring Security,并且我很担心“Acunetix WVS”工具发现HTML页面没有CSRF保护。 - Ravindu
你能详细说明它为什么不起作用吗?你能提供你的安全配置吗?渲染后的HTML是什么样子?日志看起来怎么样?你有按照这里的说明操作吗:http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#csrf-using - Rob Winch
不用在意我的问题了...我看到你已经接受了一个答案。很高兴看到你解决了它。 - Rob Winch
@RobWinch 我跟随了那个链接。问题是我正在使用 Spring Security 3.1.x 版本。在那里,我必须手动完成这个过程。这意味着我必须生成一个唯一的令牌,并将其与每个请求一起发送并验证。 - Ravindu
@Ravindu,你能分享一下你是如何解决服务器端验证的吗?我正在使用Spring Security 3.2.5.RELEASE - Manish Kumar
2个回答

9

OWASP企业安全API提供了一种非常好的选项,可以提供可靠的CSRF保护。实际上,CSRF很容易解决。OWASP ESAPI提供了以下实施CSRF保护的规范。

1. 生成新的CSRF令牌,并在登录时将其添加到用户中,然后将用户存储在http会话中。

这是默认ESAPI实现中完成的,它被存储为User对象的成员变量,该对象被存储在session中。

/this code is in the DefaultUser implementation of ESAPI
/** This user's CSRF token. */
private String csrfToken = resetCSRFToken();
...
public String resetCSRFToken() {
    csrfToken = ESAPI.randomizer().getRandomString(8, DefaultEncoder.CHAR_ALPHANUMERICS);
    return csrfToken;
}

2. 在任何需要受保护的表单或URL中,将令牌添加为参数/隐藏字段。

对于任何需要CSRF保护的URL,应调用下面的addCSRFToken方法。或者,如果您正在创建一个表单,或者有另一种呈现URL的技术(例如c:url),那么请确保添加一个名为“ctoken”且值为DefaultHTTPUtilities.getCSRFToken()的参数或隐藏字段。

//from HTTPUtilitiles interface
final static String CSRF_TOKEN_NAME = "ctoken";
//this code is from the DefaultHTTPUtilities implementation in ESAPI
public String addCSRFToken(String href) {
    User user = ESAPI.authenticator().getCurrentUser();
    if (user.isAnonymous()) {
        return href;
    }
    // if there are already parameters append with &, otherwise append with ?
    String token = CSRF_TOKEN_NAME + "=" + user.getCSRFToken();
    return href.indexOf( '?') != -1 ? href + "&" + token : href + "?" + token;
}
...
public String getCSRFToken() {
    User user = ESAPI.authenticator().getCurrentUser();
    if (user == null) return null;
    return user.getCSRFToken();
}

3. 在服务器端对于那些受保护的操作,检查提交的令牌是否与会话中用户对象的令牌匹配。

确保从您的servletspring动作或jsf控制器调用此方法,或者您使用的任何服务器端机制来处理请求。这应该在需要验证CSRF保护的任何请求上调用。请注意,当令牌不匹配时,它被认为是可能伪造的请求。

//this code is from the DefaultHTTPUtilities implementation in ESAPI
public void verifyCSRFToken(HttpServletRequest request) throws IntrusionException {
    User user = ESAPI.authenticator().getCurrentUser();
    // check if user authenticated with this request - no CSRF protection required
    if( request.getAttribute(user.getCSRFToken()) != null ) {
        return;
    }
    String token = request.getParameter(CSRF_TOKEN_NAME);
    if ( !user.getCSRFToken().equals( token ) ) {
        throw new IntrusionException("Authentication failed", "Possibly forged HTTP request without proper CSRF token detected");
    }
}

4. 在注销和会话超时时,用户对象将从会话中移除并销毁会话。

在这一步骤中,调用了注销。当这种情况发生时,请注意会话被无效化,并且当前用户对象被重置为匿名用户,从而删除了对当前用户的引用以及相应的 csrf 令牌。

//this code is in the DefaultUser implementation of ESAPI
public void logout() {
    ESAPI.httpUtilities().killCookie( ESAPI.currentRequest(), ESAPI.currentResponse(), HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME );
    HttpSession session = ESAPI.currentRequest().getSession(false);
    if (session != null) {
        removeSession(session);
        session.invalidate();
    }
    ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(), ESAPI.currentResponse(), "JSESSIONID");
    loggedIn = false;
    logger.info(Logger.SECURITY_SUCCESS, "Logout successful" );
    ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
}

来源: http://www.jtmelton.com/2010/05/16/the-owasp-top-ten-and-esapi-part-6-cross-site-request-forgery-csrf/

希望这能帮到你。

Shishir


6

显然我使用的是spring security 3.1.4.RELEASE版本。在这个版本中需要手动做此操作。然后我将其更改为3.2.2.RELEASE版本,然后我只需使用


<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

请参考以下链接,了解Spring Security 3.2的新功能:http://docs.spring.io/spring-security/site/docs/3.2.0.RELEASE/reference/htmlsingle/#new 要注意的是,从3.1.4.RELEASE升级到3.2.2.RELEASE时,需要进行许多令人困惑的重构工作,特别是在web.xml和spring-security.xml文件中。请小心操作。

我需要在控制器端编写代码来验证服务器端的令牌,还是Spring会自动处理?AJAX呢?我正在使用Spring Security 3.2.5.RELEASE - Manish Kumar
@Manish,你不需要手动进行任何服务器端验证。Spring会处理它。 - Ravindu
@Manish,请检查我的答案,了解如何在JavaScript中使用CSRF令牌。 https://stackoverflow.com/questions/22631347/send-csrf-token-inside-javascript-post-gives-an-error - Ravindu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接