CSRF token generation

Viewed 99357

This is a question about generating CSRF tokens.

Usually I'd like to generate a token based off of a unique piece of data associated with the user's session, and hashed and salted with a secret key.

My question is in regards to generating tokens when there is NO unique user data to use. No sessions are available, cookies are not an option, IP address and things of that nature are not reliable.

Is there any reason why I cannot include the string to hash as part of the request as well? Example pseudocode to generate the token and embed it:

var $stringToHash = random()
var $csrfToken = hash($stringToHash + $mySecretKey)
<a href="http://foo.com?csrfToken={$csrfToken}&key={$stringToHash}">click me</a>

Example server-side validation of the CSRF token

var $stringToHash = request.get('key')
var $isValidToken = hash($stringToHash + $mySecrtKey) == request.get('csrfToken')

The string being used in the hash would be different on each request. As long as it was included in each request, the CSRF token validation could proceed. Since it is new on each request and only embedded in the page, outside access to the token would not be available. Security of the token then falls to the $mySecretKey being known only to me.

Is this a naive approach? Am I missing some reason why this cannot work?

Thanks

9 Answers

I think the best idea to make hash based on HMAC, i.e. make hash encrypted by some password this sequence: username+user_id+timestamp. Each request the hash must be different, timestamp must be if you don't want to get simple replay the hash in attack.

You simply just need the same "token" in the URL/form and in the cookie. This means that you could have your page setting the token cookie to whatever it wants to (preferably some random value) by JavaScript and then just pass the very same value in all requests that goes to your server (as a URI ?param or form-field). No need to have your server generating the cookie.

This is safe as long as we trust that the browser doesn't allow pages from a domain to edit/read cookies for other domains, and this is assumed to be quite secure today.

Having your server generating the token will assume that this token can be safely transmitted to your browser without being picked up by any CSRF attempts (why take the risk?). Though you could put more logic into a server generated token, but to prevent CSRF there is no need.

(If I'm wrong here please let me know)

There are multiple implementation of CSRF token. The key thing is whether this csrf token is generated on the client side or server side. Because the implementation changes drastically for these two scenarios and the entropy of the token as well.

For server side, SecureRandom is the preferred way but in your case you want to generate the CSRF token before any user is identified, window.crypto provides this functionality where you can generate a unguessable enough string to be used for CSRF token.

With the help of CSRF token we can sure incoming request is authenticated (know user not hacker)

Please note i have required below approach but google can't help me even on stackoverflow i did't get mentioned code below but after collection of stackoverflow answer i have made my day. So it's useful for further searching/ specially for beginners

I have described below Spring MVC with Spring Interceptor

Note - I have used google cache to store salt in cache for re verification

below dependency need to add pom.xml

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.0-jre</version>
    </dependency>

below HandlerInterceptorAdapter implemention


    package com.august.security;

    import java.security.SecureRandom;
    import java.util.Enumeration;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;

    import org.apache.commons.lang3.RandomStringUtils;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;

    public class CsrfSecurity extends HandlerInterceptorAdapter {
        List<String> urlList= new LinkedList<>();
        private static final String CSRF_TAG = "CSRF-CHECK";

        @SuppressWarnings("unchecked")
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handleer)
                throws Exception {
            System.out.println("Inside Pre Handler");

            String reqUrl = request.getRequestURI().toString();
            System.out.println("Request URL : " + reqUrl);
            String ipAddress = request.getHeader("X-FORWARDED-FOR");
            if (ipAddress == null) {
                ipAddress = request.getRemoteAddr();
            }
            //local host url http://localhost:8080/august/
            if (request.getRequestURI().contains("/august/")) {
                System.out.println("pre handler return true");
                //it will return and next executed postHandelr method
                //because of on above url my webApplication page working
                return true;
            }
            if (ignoreUrl().contains(request.getRequestURI())) {
                System.out.println("inside ignore uri");
                return true;
            } else {
                System.out.println("CSRF Security intercepter preHandle method started.......");
                String salt = request.getParameter("csrfPreventionSalt");
                HttpSession sessionAttribute = request.getSession();
                Cache<String, Boolean> csrfPreventionSalt = (Cache<String, Boolean>) sessionAttribute
                        .getAttribute("csrfPreventionSalt");
                if (csrfPreventionSalt == null) {
                    System.out.println("Salt not matched session expired..");
                    parameterValuesPrint(request, "saltCacheNotFound");
                    response.sendRedirect("error");
                    return false;
                } else if (salt == null) {
                    parameterValuesPrint(request, "noSaltValue");
                    System.out.println("Potential CSRF detected !! inform ASAP");
                    response.sendRedirect("error");
                    return false;
                } else if (csrfPreventionSalt.getIfPresent(salt) == null) {
                    System.out.println("saltValueMisMatch");
                    System.out.println("Potential CSRF detected !! inform ASAP");
                    response.sendRedirect("error");
                } else {
                    request.setAttribute("csrfPreventionSalt", csrfPreventionSalt);
                }
                return true;
            }

        }

        @SuppressWarnings("unchecked")
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) {
            System.out.println("Inside post Handler");
            System.out.println("CSRF Security key generator method started");
            try {
                //localhost url http://localhost:8080/august/
                //api is my controller path so no need to genrate token for api
                if (request.getRequestURI().contains("/august/api/")) {
                    System.out.println("No need to genrate salt for api");
                } else {
                    HttpSession sessionAttribute = request.getSession();
                    Cache<String, Boolean> csrfPreventionSaltCache = (Cache<String, Boolean>) sessionAttribute
                            .getAttribute("csrfPreventionSalt");
                    System.out.println("csrfPreventionSaltCache ::: " + csrfPreventionSaltCache);
                    if (csrfPreventionSaltCache == null) {
                        csrfPreventionSaltCache = CacheBuilder.newBuilder().maximumSize(5000)
                                .expireAfterWrite(20, TimeUnit.MINUTES).build();
                        request.getSession().setAttribute("csrfPreventionSaltCache", csrfPreventionSaltCache);
                    }

                    String salt = RandomStringUtils.random(20, 0, 0, true, true, null, new SecureRandom());
                    System.out.println("csrfPreventionSalt genrated ::: " + salt);
                    csrfPreventionSaltCache.put(salt, Boolean.TRUE);
                    if (modelAndView != null) {
                        System.out.println("Model and view not null and salt is added in modelAndView");
                        modelAndView.addObject("csrfPreventionSalt", salt);
                    }
                }
            } catch (Exception ex) {
                System.out.println(ex.getMessage());
                ex.printStackTrace();
            }
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            System.out.println("afterCompletion : ");
            if (ex != null) {
                System.out.println("exception : " + ex.getMessage());
                ex.printStackTrace();
            }
        }

        private List<String> ignoreUrl() {
            if(urlList == null) {
                urlList.add("/august/error");
                //add here your ignored url.
            }
            return urlList;
        }

        private void parameterValuesPrint(HttpServletRequest request, String err) {
            StringBuilder reqParamAndValue = new StringBuilder();
            Enumeration<?> params = request.getParameterNames();
            while (params.hasMoreElements()) {
                Object objOri = params.nextElement();
                String param = (String) objOri;
                String value = request.getParameter(param);
                reqParamAndValue = reqParamAndValue.append(param + "=" + value + ",");
            }
            System.out.println(CSRF_TAG + " " + err + "RequestedURL : " + request.getRequestURL());
        }
    }

Below is Interceptor registration with spring context


package com.august.configuration;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;

    import com.august.security.CsrfSecurity;

    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages="com.august")
    public class SpringConfiguration extends WebMvcConfigurerAdapter  {

        @Bean
        public ViewResolver viewResolver() {
            InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
            //viewResolver.setViewClass(JstlView.class);
            viewResolver.setPrefix("/WEB-INF/views/");
            viewResolver.setSuffix(".jsp");
            return viewResolver;

        }

        @Bean
        public CsrfSecurity csrfSecurity() {
            return new CsrfSecurity();
        }
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new CsrfSecurity());
        }
    }

below is my controller


    package com.august.v1.appcontroller;

    import javax.servlet.http.HttpSession;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;


    @Controller
    public class HomeController {

        @Autowired 
        HttpSession httpSession;

        @RequestMapping("/")
        public String index(Model model) {
            httpSession.invalidate();
            System.out.println("Home page loaded");
            return "index";
        }
    }

below is my index.jsp jsp page


    <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
        pageEncoding="ISO-8859-1" isELIgnored="false"%>
         //don't forget to add isELIgnored="false" on old(version) jsp page because of i 
         //have wasted 1 hour for this
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>ADS Home</title>
    </head>
    <body>
    <h1>${csrfPreventionSalt}</h1>
    <input type="hidden" name="csrfPreventionSalt" value=${csrfPreventionSalt}>
    </body>
    </html>

For Understanding about CSRF - CSRF explanation

Related