We use something like

/**
 * A {@link Form} that adds cross-site request forgery protection.
 *
 * @param <T>
 *            The model object type
 */
public class CSRFForm<T> extends Form<T> {

    // serves as a mean to transfer CSRF protection token between
server and client (and vice versa)
    private String clientCSRFToken;
    private final String serverCSRFToken;

    private HiddenField<String> csrfTokenHiddenField;

    // we can't use LogCategory as it is not visible here.
    private static final Logger LOGGER = LoggerFactory.getLogger("Security");

    public CSRFForm(String id) {
       super(id);
       clientCSRFToken = generateCSRFToken();
       // keep an immutable copy on the server side.
       serverCSRFToken = clientCSRFToken;
    }

    public CSRFForm(String id, IModel<T> model) {
       super(id, model);
       clientCSRFToken = generateCSRFToken();
       // keep an immutable copy on the server side.
       serverCSRFToken = clientCSRFToken;
    }

    /**
     * Override to use a different way to generate a token.
     *
     * @return a CSRF token
     */
    protected String generateCSRFToken() {
       return UUID.randomUUID().toString();
    }

    @Override
    protected void onInitialize() {
       super.onInitialize();
       // client will submit clientCSRFToken, and it will be set in
clientCSRFToken on the server side
       // initially server sets this value, and it is expecting it
back, unchanged, from the client
       csrfTokenHiddenField = new HiddenField<>("CRSFFormVersion",
LambdaModel.of(CSRFForm.this::getClientCSRFToken,
CSRFForm.this::setClientCSRFToken), String.class);
       add(csrfTokenHiddenField);
    }

    @Override
    protected void delegateSubmit(IFormSubmitter submittingComponent) {
       // we compare the version stored on the server with the version
the client sent
       // form is only processed
       if (serverCSRFToken.equals(clientCSRFToken)) {
          super.delegateSubmit(submittingComponent);
       } else {
          String message = String.format("Preventing possible CSRF
attack! Client clientCSRFToken=%s and server serverCSRFToken=%s do not
match!", clientCSRFToken, serverCSRFToken);
          LOGGER.error(message);
          throw new WicketRuntimeException(message);
       }
    }

    @Override
    public void onComponentTagBody(MarkupStream markupStream,
ComponentTag openTag) {
       getResponse().write("<input type=\"hidden\" class=\"csrf-form\"
name=\"" + csrfTokenHiddenField.getInputName() + "\" value =\"" +
Strings.escapeMarkup(serverCSRFToken) + "\"/>");
       super.onComponentTagBody(markupStream, openTag);
    }

    public String getClientCSRFToken() {
       return clientCSRFToken;
    }

    public void setClientCSRFToken(String clientCSRFToken) {
       this.clientCSRFTokaen = clientCSRFToken;
    }
}

we also use a in order to spot non-secure forms

public class CSRFFromChecker implements IComponentInitializationListener {

    private static final CSRFFromChecker INSTANCE = new CSRFFromChecker();

    // we can't use LogCategory as it is not visible here.
    private static final Logger LOGGER = LoggerFactory.getLogger("Security");

    public static CSRFFromChecker getInstance() {
       return INSTANCE;
    }

    @Override
    public void onInitialize(Component component) {
       if (component instanceof Form<?> && !(component instanceof
CSRFForm<?>)) {
          LOGGER.warn("Form {} is not a form or CSRF form",
component.getPath());
       }
    }
}


Ernesto Reinaldo Barreiro
Apache Wicket Committer

Reply via email to