Предотвращение CSRF в JSF2 с сохранением состояния на стороне клиента
Я использую MyFaces 2.2.3 с сохранением состояния на стороне клиента + PrimeFaces
После того, как я спросил , как предотвратить повторное использование ViewState в различных сеансах, Мнесказал BalusC , Что я могу ввести мой собственный CSRF-токен с помощью переопределить отрисовщик, чтобы значение было CSRF-токеном,
Я ищу решение, которое не заставит меня изменять моиxhtml страницы вообще :)
BalusC имеет предложил лучший способ предотвратить атаку CSRF путем расширения ViewHandlerWrapper
, и это отлично работает, мне только пришлось немного изменить 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
.
1 ответ:
Этот подход переопределения рендера
<h:form>
небезопасен для примитивовpartialSubmit="true"
. Кроме того, повторное использование его скрытого поля, идентифицирующего представленную форму, было бы специфичной реализацией JSF, поскольку это не является частью API JSF.С другой стороны, гораздо проще хранить маркер 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); }