49

这是一个关于生成 CSRF 令牌的问题。

通常我想根据与用户会话相关联的唯一数据生成一个令牌,并使用密钥进行散列和加盐。

我的问题是关于在没有唯一用户数据可供使用时生成令牌的问题。没有可用的会话,cookie 不是一个选项,IP 地址和这种性质的东西是不可靠的。

有什么理由我不能将要散列的字符串也包含在请求中吗?生成令牌并嵌入它的示例伪代码:

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

CSRF 令牌的示例服务器端验证

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

哈希中使用的字符串在每个请求中都会有所不同。只要它包含在每个请求中,CSRF 令牌验证就可以继续进行。由于它在每个请求中都是新的并且仅嵌入在页面中,因此无法从外部访问令牌。然后令牌的安全性落到只有我知道的 $mySecretKey 上。

这是一种天真的方法吗?我错过了为什么这不起作用的一些原因吗?

谢谢

4

9 回答 9

29

有什么理由我不能将要散列的字符串也包含在请求中吗?

CSRF 令牌有两个部分。嵌入在表单中的令牌,以及其他地方的相应令牌,无论是在 cookie 中、存储在会话中还是在其他地方。这种在别处的使用会阻止页面自包含。

如果您在请求中包含要散列的字符串,则请求是自包含的,因此复制表单是攻击者需要做的所有事情,因为他们拥有令牌的两个部分,因此没有保护。

即使将它放在表单 URL 中也意味着它是自包含的,攻击者只需复制表单和提交 URL。

于 2009-12-04T09:14:55.307 回答
8

试试base64_encode(openssl_random_pseudo_bytes(16))https://github.com/codeguy/php-the-right-way/issues/272#issuecomment-18688498我在https://gist.github.com/mikaelz/5668195中将它用于我的表单示例

于 2013-05-31T10:12:52.720 回答
2

CSRF 令牌旨在防止(无意的)数据修改,这通常与 POST 请求一起应用。

因此,您必须为每个更改数据的请求(GET 或 POST 请求)包含 CSRF 令牌。

我的问题是关于在没有唯一用户数据可供使用时生成令牌的问题。没有可用的会话,cookie 不是一个选项,IP 地址和这种性质的东西是不可靠的。

然后只需为每个访问者创建一个唯一的用户 ID。在 cookie 或 URL 中包含该 id(如果 cookie 被禁用)。

编辑:

考虑以下事件:

您已经登录到您的 Facebook 帐户,然后进入某个任意网站。

在那个网站上,有一个您提交的表单,它告诉您的浏览器向您的 Facebook 帐户发送一个 POST 请求。

该 POST 请求可能会更改您的密码或添加评论等,因为 Facebook 应用程序将您识别为注册和登录用户。(除非有另一种阻塞机制,例如 CAPTCHA )

于 2009-11-26T22:06:42.987 回答
2

我认为基于 HMAC 制作散列的最佳想法,即通过以下顺序使用某个密码对散列进行加密:用户名+用户 ID+时间戳。每个请求的哈希值必须不同,如果您不想在攻击中简单地重放哈希值,时间戳必须是不同的。

于 2012-03-27T12:30:19.243 回答
1

我想说你的方法有效,因为CSRF攻击是攻击者利用受害者的浏览器来伪造登录状态,他们为什么可以这样做?因为在大多数服务器端,会话检查是基于 cookie 中的 SessionID,而 cookie 是一条数据,将自动附加到发送到服务器的 HTTP 请求。

因此,捍卫 CSRF 有两个关键因素

  1. 生成挑战令牌,并要求客户端以非 cookie 的方式将其传递给服务器,URL 参数或 POST 形式都可以。
  2. 保持令牌安全,就像您对 SessionID 所做的一样,例如,使用 SSL。

我建议阅读 CSRF 预防备忘单

于 2014-10-19T09:15:23.820 回答
1

您只需要在 URL/表单和 cookie 中使用相同的“令牌”。这意味着您可以让您的页面通过 JavaScript 将令牌 cookie 设置为它想要的任何值(最好是一些随机值),然后在发送到您的服务器的所有请求中传递相同的值(作为 URI ?param 或 form-场地)。无需让您的服务器生成 cookie。

只要我们相信浏览器不允许来自域的页面编辑/读取其他域的 cookie,这是安全的,并且这在今天被认为是非常安全的。

让您的服务器生成令牌将假定此令牌可以安全地传输到您的浏览器,而不会被任何 CSRF 尝试拾取(为什么要冒险?)。虽然您可以将更多逻辑放入服务器生成的令牌中,但为了防止 CSRF,没有必要。

(如果我在这里错了,请告诉我)

于 2010-03-04T23:00:59.897 回答
1

在 CSRF 令牌的帮助下,我们可以确定传入的请求是经过身份验证的(知道用户不是黑客)

请注意,我需要以下方法,但即使在 stackoverflow 上,谷歌也无法帮助我,我没有得到下面提到的代码,但是在收集了 stackoverflow 答案后,我度过了愉快的一天。所以它对于进一步搜索/特别适合初学者很有用

我在下面描述了带有 Spring Interceptor 的 Spring MVC

注意 - 我已经使用谷歌缓存将盐存储在缓存中以进行重新验证

下面的依赖需要添加 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>

下面 HandlerInterceptorAdapter 实现


    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());
        }
    }

下面是带有弹簧上下文的拦截器注册


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());
        }
    }

下面是我的控制器


    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";
        }
    }

下面是我的 index.jsp jsp 页面


    <%@ 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>

了解 CSRF - CSRF 解释

于 2019-10-02T04:55:43.607 回答
1

CSRF 令牌有多种实现方式。关键是这个 csrf 令牌是在客户端还是服务器端生成的。因为这两种情况的实现发生了巨大变化,令牌的熵也发生了巨大变化。

对于服务器端,SecureRandom 是首选方式,但在您的情况下,您希望在识别任何用户之前生成 CSRF 令牌,window.crypto提供了此功能,您可以在其中生成足够难以猜测的字符串以用于 CSRF 令牌。

于 2018-05-30T23:54:40.640 回答
0

CSRF 利用用户的会话,因此,如果您没有会话,则没有 CSRF。

于 2009-12-04T09:10:56.190 回答