什么时候应该使用服务器端会话而不是客户端会话?

信息安全 饼干 csrf 会话管理
2021-09-09 11:52:06

从信息安全的角度来看,什么时候应该启用服务器端会话而不是客户端会话?

据我所知,“安全客户端会话”是包含以用户可以“查看”其内容的方式签名的数据的 cookie,但在服务器不知道的情况下无法修改它们。我有点困惑这是否意味着 cookie 的内容是加密的,或者内容是人类可读的。例如, Flask对数据进行加密。如果数据是加密的,那么在什么情况下将会话数据存储在 cookie 中是不安全的?例如,将 csrf-token 放在客户端 cookie 中是否安全,无论它是否加密?

另一方面,“服务器端会话”有两部分:仅包含会话 ID 的 cookie,以及包含会话 ID 和数据的数据库条目。在这个实现中,用户不能访问会话数据。

2个回答

例如,将 csrf-token 放在客户端 cookie 中是否安全,无论它是否加密?

是的。OWASP 调用此方法Double Submit Cookies我在实践中从未见过它。

这是安全的原因是 CSRF 令牌是临时值。例如,如果您将加密密码存储在客户端会话中,那将非常糟糕(取决于密钥和加密算法)。攻击者可能能够窃取它、解密它,然后获得用户密码。对于 CSRF 令牌,攻击者一无所获。此外,如果攻击者或用户更改 cookie,它不会影响服务器安全,所发生的只是令牌不起作用。

在什么情况下将会话数据存储在 cookie 中是不安全的?

这里有两件事需要担心:

  • 用户或攻击者可能会更改 cookie 中的数据,而服务器将不知道发生了这种情况,接受更改的数据(例如,它可能包含一个值,例如isAdmin:0,可能会更改为isAdmin:1)。
  • cookie 可能包含用户或攻击者可能读出的敏感信息

为了解决第一点,可以使用 MAC,为了解决第二点,cookie 数据应该被加密。

在创建客户端会话之前,您应该问自己是否真的需要它(如果您有多个服务器需要共享相同的会话状态,例如出于可伸缩性原因,可能会出现这种情况)。会话数据传统上是在服务器端处理的,原因是:它包含客户端不应读取或更改的数据。最简单的方法是不将其发送给客户端。

建立一个复杂的系统来存储客户端的会话数据是很困难的,而且这样做可能会出错。如果没有必要,只存储数据服务器端。

什么时候应该启用服务器端会话而不是客户端会话?

我想不出除了“用户体验”之外您还想要客户端的情况。

会话标识符应始终位于服务器端,因为服务器应验证会话是否有效。

将 CSRF 令牌放在客户端 cookie 中是否安全,无论它是否加密?

反 CSRF 令牌应该包含随机生成的数据,这比生成加密字符串“便宜”得多,只要字符串足够长且足够随机。

我认为将反 CSRF 令牌存储在 cookie 中是不够的。

我建议创建一个系统,其中从隐藏的输入字段提交反 CSRF 令牌并在标头中传输。两者都应该在服务器端进行检查。

这是 PHP 中的一个示例:

function generateToken($key) {
  $token = base64_encode(openssl_random_pseudo_bytes(16));
  $_SESSION['csrf_' . $key] = $token;
  return $token;
}


function checkToken($key, $value) {
  if (!isset($_SESSION['csrf_' . $key]))
    return false;
  if (!$value)
    return false;
  if ($_SESSION['csrf_' . $key] !== $value)
    return false;

  unset($_SESSION['csrf_' . $key]);
  return true;
}


  if ($_POST and $_POST['action'] == "something")
  {
    $header_token = apache_request_headers()['X-Anti-Csrf-Token'];
    $post_token = $_POST['token'];
    $post_token = str_replace(" ", "+", $post_token);

    if ($header_token == $post_token)
    {
        if (checkToken('settings', $post_token))
        {   
           // ok; do something

        }
     }
     else
     { 
         // wrong; do something
     }


 }

在处理之前发送标头:

   <script>
    $("#some_div").submit(function(event) {
      event.preventDefault();

      var $form = $(this),
        url = $form.attr('action');

      var posting = $.ajax(url, {
        type: 'POST',
        processData: true,
        dataType: "text",
        beforeSend: function (xhr) {
        xhr.setRequestHeader('X-Anti-CSRF-Token', $('#token').val());
    }

这段代码将生成令牌并将其放入隐藏的输入字段中。

<input id="token" type="hidden" value="<?php echo generateToken('settings'); ?>">