通过客户端状态保存防止 JSF2 中的 CSRF

Posted

技术标签:

【中文标题】通过客户端状态保存防止 JSF2 中的 CSRF【英文标题】:Prevent CSRF in JSF2 with client side state saving 【发布时间】:2015-08-11 00:14:52 【问题描述】:

我正在使用 MyFaces 2.2.3 和客户端状态保存 + PrimeFaces

在询问how to prevent the re-use of a ViewState in different sessions 我是told by BalusC 之后,我可以注入我自己的CSRF 令牌,方法是覆盖来自渲染器,让值成为CSRF 令牌 ,

我正在寻找一种完全不会强迫我修改我的 xhtml 页面的解决方案 :)


BalusC 提出了一种更好的方法来通过扩展 ViewHandlerWrapper 来防止 CSRF 攻击,并且效果很好,我只需要通过以下方式对 restoreView 进行一点修改

public UIViewRoot restoreView(FacesContext context, String viewId) 
    UIViewRoot view = super.restoreView(context, viewId);
    if (getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY))) 
        return view;
     else 
        HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
        if (session != null) 
            session.invalidate(); //invalidate session so (my custom and unrelated) PhaseListener will notice that its a bad session now
        
        try 
            FacesContext.getCurrentInstance().getExternalContext().redirect("CSRF detected and blocked"); //better looking user feedback
         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    


旧解决方案

到目前为止我尝试过没有成功,

添加到 faces-config.xml

<render-kit>
    <renderer>
        <component-family>javax.faces.Form</component-family>
        <renderer-type>javax.faces.Form</renderer-type>
        <renderer-class>com.communitake.mdportal.renderers.CTFormRenderer</renderer-class>
    </renderer>
</render-kit>   

然后在CTFormRenderer.java

@Override
public void encodeEnd(FacesContext context, UIComponent arg1) throws IOException 
    //how to set form value be a CSRF token?


@Override
public void decode(FacesContext context, UIComponent component) 
    HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
    String token = (String) session.getAttribute(CSRFTOKEN_NAME);
    String tokenFromForm = //how to get the value stored in form value attribute because (String) component.getAttributes().get("value"); return null
    //check token against tokenFromForm...

我不想为每个h:form 添加自定义组件,而是想扩展form 渲染器,这样我的所有表单都将具有csrf token

【问题讨论】:

它在没有 PrimeFaces 的情况下是否可以使用纯 jsf(只是为了确定)?如果没有,它是否适用于 Mojarra? 我对您的方向并不完全清楚:“我可以通过覆盖 from 渲染器来注入我自己的 CSRF 令牌,让值成为 CSRF 令牌”这是否意味着您要指定 CSRF手动令牌?你知道它必须与服务器最初生成的内容相对应吗? @kolossus ,您可以在我之前的问题和 BalusC 的回答 ***.com/questions/30373089/… 中详细了解我为什么需要这个解决方案 您是否也打算生成此令牌?或者您想检索股票 JSF 值? 我打算自己生成 【参考方案1】:

这种&lt;h:form&gt; 渲染器覆盖方法对 PrimeFaces partialSubmit="true" 不安全。此外,重用其标识提交表单的隐藏字段将是特定于 JSF 实现的,因为这不是 JSF API 的一部分。

再想一想,将 CSRF 令牌直接存储在 JSF 视图状态本身中要简单得多。您可以使用自定义 ViewHandler 来实现这一点,如下所示,它在 UIViewRoot 中设置一个属性(自动保存在 JSF 视图状态中):

public class CsrfViewHandler extends ViewHandlerWrapper 

    private static final String CSRF_TOKEN_KEY = CsrfViewHandler.class.getName();

    private ViewHandler wrapped;

    public CsrfViewHandler(ViewHandler wrapped) 
        this.wrapped = wrapped;
    

    @Override
    public UIViewRoot restoreView(FacesContext context, String viewId) 
        UIViewRoot view = super.restoreView(context, viewId);
        return getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY)) ? view : null;
    

    @Override
    public void renderView(FacesContext context, UIViewRoot view) throws IOException, FacesException 
        view.getAttributes().put(CSRF_TOKEN_KEY, getCsrfToken(context));
        super.renderView(context, view);
    

    private String getCsrfToken(FacesContext context) 
        String csrfToken = (String) context.getExternalContext().getSessionMap().get(CSRF_TOKEN_KEY);

        if (csrfToken == null) 
            csrfToken = UUID.randomUUID().toString();
            context.getExternalContext().getSessionMap().put(CSRF_TOKEN_KEY, csrfToken);
        

        return csrfToken;
    

    @Override
    public ViewHandler getWrapped() 
        return wrapped;
    


请注意,当restoreView() 返回null 时,JSF 将“照常”抛出ViewExpiredException

要让它运行,请在faces-config.xml注册如下:

<application>
    <view-handler>com.example.CsrfViewHandler</view-handler>    
</application>

因为它对服务器端状态保存没有附加价值,如果当前 JSF 应用程序配置了客户端状态保存,如果需要,您可以在视图处理程序的构造函数中检测如下:

FacesContext context = FacesContext.getCurrentInstance();

if (!context.getApplication().getStateManager().isSavingStateInClient(context)) 
    throw new IllegalStateException("This view handler is only applicable when JSF is configured with "
        + StateManager.STATE_SAVING_METHOD_PARAM_NAME + "=" + StateManager.STATE_SAVING_METHOD_CLIENT);

【讨论】:

谢谢!看起来正是我需要的,明天将测试它:) b.t.w 是否可以重定向到一些自定义错误页面以防不等于而不是返回 null 并显示 ViewExpiredException 页面? 检查了它,尽管 getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY)) 是 false 并且在 restoreView 中返回了 null ,jsf 仍然继续使用 @987654334 的 action 方法 @ 和外部 html 仍然设法发布其数据,但如果我将 return null; 替换为 FacesContext.getCurrentInstance().getExternalContext().redirect("somePage.jsf"); return null将不会继续执行操作方法 嗯,应用中还有更多的视图处理程序吗? 不,它是唯一的一个,实际上重定向为我提供flow hijack 它并不是它实际上重定向,因为外部表单提交返回一个xml并且浏览器显示:This XML file does not appear to have any style information associated with it. The document tree is shown below. &lt;partial-response&gt; &lt;redirect url="somePage.jsf"/&gt; &lt;/partial-response&gt;跨度> 无法重现 JSF 仍继续使用 MyFaces 2.2.8 的操作方法。您使用哪些上下文参数设置?

以上是关于通过客户端状态保存防止 JSF2 中的 CSRF的主要内容,如果未能解决你的问题,请参考以下文章

TOKEN验证防止CSRF攻击的原理

Cookie:SameSite,防止CSRF攻击

在基于 OAuth2 的身份验证中,状态参数可以防止啥样的 CSRF 攻击?

django-allauth 移动客户端 csrf 保护

hoj 2662 经典状压dp // MyFirst 状压dp

用于ajax调用的spring security csrf保护