在其他会话中重用 ViewState 值 (CSRF)

Reusing ViewState value in other session (CSRF)

我正在使用 *myfaces-api-2.2.3,javax.faces.STATE_SAVING_METHOD 设置为 client ,

我得到了以下场景,

1) 用户 X 登录系统并添加用户 XXX(使用 jsf f:ajax 操作),同时检查 chrome 开发工具,您可以查看与 ViewState 值一起提交的表单。

2) 复制 ViewState 值(来自 chrome 开发工具 --> 网络选项卡)--> 放置它进入 html 格式的文件(模仿我原来的 添加用户 X

3) 从用户 X 会话注销(会话在此过程中失效)

4) 以用户 Y 登录 --> 在浏览器中打开 that html 文件并点击提交按钮表单 --> 您会注意到添加了用户 XXX(尽管表单中使用的 ViewState 值属于其他用户(用户 X)。

我认为 ViewState 值不能以这种方式使用,我认为它应该防止这种行为,为什么可以使用一个 ViewState 值在拥有自己的 ViewState 值的全新会话中以及如何确保用户无法重用 ViewState ?


查看我的其他问题和BalusC answer :

这是 specified/expected 使用 客户端 状态保存时的行为。 JSF 视图状态不保存在会话中,而是完整地保存在客户端 HTML 表单中的隐藏输入字段中。仅当使用服务器端状态保存时,JSF 视图状态在用户的 HTTP 会话中不存在时才会失效。

我在 MyFaces 中没有看到 any way 使客户端保存的状态无效,以防它在回发期间与“错误”会话重新关联。 org.apache.myfaces.CLIENT_VIEW_STATE_TIMEOUT 上下文参数接近。您可以将其设置为与会话超时时间相同。

CSRF 攻击场景在我看来有点夸张。首先,攻击者需要能够掌握客户端状态。最简单的方法是 XSS 漏洞。只是,JSF 到处都有 XSS 攻击预防(如果你小心 escape="false")。最难的方法是掌握整个 HTML 输出。这可以通过中间人攻击来完成。只是,这也为攻击者提供了整个会话 cookie,因此在这种情况下,整个 CSRF 攻击场景对攻击者来说“不必要地过于复杂”,因为攻击者可以简单地劫持会话。最好的保护就是简单地使用 HTTPS 而不是 HTTP。

即便如此,如果攻击者以某种方式掌握了客户端视图状态,攻击者在不对其进行解密、反序列化和操作的情况下也无法对其进行很多危险操作。 MyFaces 默认对其加密很长时间(从早期 2.0.x 开始)。攻击者无法在没有正确密钥的情况下解密它,因此也无法操纵它。这种客户端视图状态加密已经顺便成为 JSF 2.2 规范的必需部分(因此 Mojarra 也这样做)。当您重新启动应用程序服务器时,默认情况下会重置此密钥(因此所有客户端状态也将失效)。如有必要,您可以通过 web.xml:

中的以下环境条目指定固定密钥
<env-entry>
    <env-entry-name>jsf.ClientSideSecretKey</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>

您可以使用 this page 生成 Base64 格式的随机 AES 密钥。

即便如此,如果攻击者以某种方式成功解密视图状态并让另一个用户实际提交它(例如通过 XSS 漏洞或网络钓鱼站点),那么您最好的选择是包含一个基于 HTTP 会话的 CSRF JSF 页面中的令牌。但如果 webapp 在某处仍然存在 XSS 攻击漏洞,或者通过 HTTP 而不是 HTTPS 提供服务,那也不安全。

另请参阅:

  • javax.faces.application.ViewExpiredException: View could not be restored
  • CSRF, XSS and SQL Injection attack prevention in JSF

Spring 安全默认不检查 GET 请求。在 CSRF 保护中,重要的是要防止危险的 POST 请求操作。因此,您可以考虑忽略具有以下代码集的 ajax partialSubmit="true" 请求。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        RequestMatcher csrfRequestMatcher = new RequestMatcher() {
            private final Pattern allowedMethods = Pattern.compile("^GET$");
            @Override
            public boolean matches(HttpServletRequest request) {

                if(request == null)
                    return false;

                if (allowedMethods.matcher(request.getMethod()).matches()) {
                    return false;
                }

                else return request.getParameter("javax.faces.partial.ajax") == null || !request.getParameter("javax.faces.partial.ajax").equals("true");
            }

        };

        http
            .anyRequest().authenticated()
            .....
            .csrf().requireCsrfProtectionMatcher(csrfRequestMatcher);
    }
}