All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ro.pippo.core.route.CSRFHandler Maven / Gradle / Ivy

There is a newer version: 1.8.0
Show newest version
/*
 * Copyright (C) 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ro.pippo.core.route;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.HttpConstants;
import ro.pippo.core.ParameterValue;
import ro.pippo.core.StatusCodeException;
import ro.pippo.core.util.CryptoUtils;
import ro.pippo.core.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;

/**
 * Handler that generates and validates a CSRF token.
 * 

* An attacker can coerce a victims browser to make the following types of requests: *

* GET requests * POST requests with a "Content-Type" of "application/x-www-form-urlencoded", "multipart/form-data", and "text/plain". *

* An attacker can not: *

* Coerce the browser to use other request methods such as PUT and DELETE. * Coerce the browser to post other content types, such as "application/json". * Coerce the browser to send new cookies, other than those that the server has already set. * Coerce the browser to set arbitrary headers, other than the normal headers the browser adds to requests. *

* Since GET requests are not meant to be mutative, there is no danger to an application that follows this * best practice. *

* Rules: *

* Permit POST if the "Content-Type" is not a guarded type (see above). * Permit POST if the "Csrf-Token" header is "nocheck". * Permit POST if the "_csrf_token" query parameter or form field matches the session csrf token. * * @author James Moger */ public class CSRFHandler implements RouteHandler { public static final String TOKEN = "_csrf_token"; public static final String BINDING = "csrfToken"; private static final Logger log = LoggerFactory.getLogger(CSRFHandler.class); private final List guardedTypes = Arrays.asList("application/x-www-form-urlencoded", "multipart/form-data", "text/plain"); private final String secretKey; private final String algorithm; /** * Constructs an CSRF handler with a dynamically generated SecretKey. */ public CSRFHandler() { this(CryptoUtils.generateSecretKey()); } public CSRFHandler(String secretKey) { this(secretKey, CryptoUtils.HMAC_SHA256); } public CSRFHandler(String secretKey, String algorithm) { this.secretKey = secretKey; this.algorithm = algorithm; } public String getSecretKey() { return secretKey; } public String getAlgorithm() { return algorithm; } protected String getSessionCsrfToken(RouteContext context) { return context.getSession(TOKEN); } protected void setSessionCsrfToken(RouteContext context, String token) { context.setSession(TOKEN, token); } protected String getTokenId(RouteContext context) { return context.getSession().getId().toString(); } @Override public void handle(RouteContext context) { // obtain the servlet http method because Pippo allows method spoofing String rawMethod = context.getRequest().getHttpServletRequest().getMethod(); if (HttpConstants.Method.POST.equals(rawMethod)) { // Verify the content-type is guarded String contentType = new ParameterValue(context.getHeader("Content-Type")).toString("").toLowerCase(); contentType = StringUtils.getPrefix(contentType, ';').trim(); if (!guardedTypes.contains(contentType)) { log.debug("Ignoring '{}' request for {} '{}'", contentType, context.getRequestMethod(), context.getRequestUri()); return; } // Permit "nocheck" Csrf-Token headers String requestToken = context.getHeader("Csrf-Token"); if ("nocheck".equals(requestToken)) { log.debug("Ignoring 'nocheck' request for {} '{}'", context.getRequestMethod(), context.getRequestUri()); return; } if (StringUtils.isNullOrEmpty(requestToken)) { requestToken = context.getParameter(TOKEN).toString(); } if (StringUtils.isNullOrEmpty(requestToken)) { throw new StatusCodeException(HttpServletResponse.SC_FORBIDDEN, "Illegal request, no '{}'!", TOKEN); } // Validate the request token against the session token String sessionToken = getSessionCsrfToken(context); if (!requestToken.equals(sessionToken)) { throw new StatusCodeException(HttpServletResponse.SC_FORBIDDEN, "Illegal request, invalid '{}'!", TOKEN); } log.debug("Validated '{}' for {} '{}'", TOKEN, context.getRequestMethod(), context.getRequestUri()); context.setLocal(BINDING, sessionToken); } else if (HttpConstants.Method.GET.equals(rawMethod)) { // Generate a CSRF session token on reads if (getSessionCsrfToken(context) == null) { String sessionId = getTokenId(context); String token = CryptoUtils.hmacDigest(sessionId, secretKey, algorithm); setSessionCsrfToken(context, token); log.debug("Generated '{}' for {} '{}'", TOKEN, context.getRequestMethod(), context.getRequestUri()); } String token = getSessionCsrfToken(context); context.setLocal(BINDING, token); } context.next(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy