【问题标题】:Is this a secure way to set the token CSRF?这是设置令牌 CSRF 的安全方法吗?
【发布时间】:2012-12-09 17:53:51
【问题描述】:

我想知道这是否是一种设置令牌的安全方法,除非确实生成了令牌,否则我会生成一个令牌,并在整个应用程序和这些表单中使用它。每个会话一个令牌?

if (!isset($_SESSION['token'])) {
    $data['token'] = uniqid(rand(), true);
    session_regenerate_id();
    $_SESSION['token'] = $data['token'];
}

是否有必要清除提交表单上的令牌?还是坚持下去,即使我提交了表格?

【问题讨论】:

  • 该代码本身是不够的。该令牌用作盐以及(一组)表单的唯一标识符,减轻了泄漏造成的损害并保持了用户的可用性。您还需要缓解有关到期的问题。如果你的代币永远可用。这是关于 CSRF 的设计缺陷。

标签: php security


【解决方案1】:

如果您不知道这些链接,this 应该可以帮助您了解一些场景,特别是this 会告诉您注意事项。希望对您有所帮助。

【讨论】:

  • 只需 +1 即可链接该材料。即使我通常不喜欢仅链接的答案,这是一个维基,其中不止一个人关心该主题。
【解决方案2】:

就我个人而言,我会为我想要显示的每个表单生成一个新令牌。如果你这样做,只要会话保持活动状态,某人只需要一个会话 cookie 来读取你的令牌并使用它。

在我的应用程序中,我为每个表单显示生成一个令牌,如下所示:

<?php
$token = uniqid(rand(), true);
$_SESSION['csrf_tokens'][$token] = true;

HTML

<form>
    <input type="hidden" name="token" value="<?php echo $token ?>" />
</form>

在表单验证时,我会像这样检查该令牌:

if (isset($_SESSION['csrf_tokens'][$token]) && $_SESSION['csrf_tokens'][$token] === true) {
    unset($_SESSION['csrf_tokens'][$token]);
    // additional code here
}

【讨论】:

  • 如果我打开不同的标签页会有什么影响?假设我为每个请求生成一个令牌,如果人们在我的网站上打开许多标签,这会产生问题
  • 用一个例子更新了我的答案
  • 每个表单一个令牌的好处是它会自动防止重复提交表单...
  • 这里的问题是如果用户刷新shite,会产生很多很多的会话......这不是问题吗?
【解决方案3】:

我想知道这是否是一种设置令牌的安全方式

这取决于您的网络应用程序需要有多安全。此行不是加密安全的(正如 PHP 文档中针对 uniqid() 和 rand() 的警告):

uniqid(rand(), true);

如果令牌生成时间已知/确定并且 rand() 种子已知/确定,则攻击者确定/暴力破解这一点可能是可行的。但是,出于您的目的,它可能没问题,因为它仍然可以防止攻击者不知道令牌值的 CSRF 攻击。

每个会话一个令牌?

每个会话使用一个令牌可能适合您的目的。但是,请注意:

  1. 如果会话长达 n 分钟,则攻击者有 n 分钟的窗口来尝试确定或获取您的令牌值并执行 CSRF 攻击。而当按表单生成令牌或定期重新生成令牌时,这种风险会降低,因为它们的寿命不够长。
  2. 在每个会话中使用单个令牌会暴露您应用程序的所有功能(使用该令牌)以在攻击者确定/获得令牌时进行攻击。而在每个表单中使用令牌会将攻击限制在单个表单中。

是否有必要清除提交表单上的令牌?还是坚持下去,即使我提交了表格?

这取决于您的应用程序的目标对攻击者的价值有多大,以及攻击会给您造成的破坏程度。您现有的措施使得执行 CSRF 攻击变得困难,但如果它具有很高的价值并且您有非常坚定的攻击者,那么您可能希望通过以下方式进一步降低 CSRF 的风险:

  1. 使用加密安全令牌来防止确定或暴力破解令牌值的风险。
  2. 定期重新生成令牌以缩短令牌寿命,如果令牌被确定或获得,则减少攻击窗口。
  3. 在确定或获得令牌的情况下,为每个表单生成令牌以将攻击限制为单一表单。

【讨论】:

    【解决方案4】:

    比起使用per-session token,我更喜欢per-form/url token 以获得额外的安全性,有些人可能会认为per-request token 是最安全的,但会影响可用性。

    我还认为最好将会话存储与令牌存储分开并使用Memcache 之类的东西。当您需要使用多个应用程序服务器等时,这会更好。我也更喜欢它,因为我可以添加自定义 expiration to the token 而不必影响整个 session

    这是一个典型的例子

    HTML

    <form method="POST" action="#">
        IP:<input type="text" name="IP" /> <input type="hidden" name="token"
            value="<?php echo Token::_instance()->generate(); ?>" /> <input
            type="Submit" value="Login" />
    </form>
    

    处理

    $id = "id44499900";
    Token::_instance()->initialise($id); // initialise with session ID , user ID or IP
    
    try {
    
        Token::_instance()->authenticate();
        // Process your form
    } catch ( TokenException $e ) {
        http_response_code(401); // send HTTP Error 401 Unauthorized
        die(sprintf("<h1>%s</h1><i>Thief Thief Thief</i>", $e->getMessage()));
    }
    

    使用的类

    class Token {
        private $db;
        private $id;
        private static $_instance;
    
        function __construct() {
            $this->db = new Memcache();
            $this->db->connect("localhost");
        }
    
        public static function _instance() {
            self::$_instance === null and self::$_instance = new Token();
            return self::$_instance;
        }
    
        public function initialise($id) {
            $this->id = $id;
        }
    
        public function authenticate(array $source = null, $key = "token") {
            $source = $source !== null ? $source : $_POST;
    
            if (empty($this->id)) {
                throw new TokenException("Token not Initialised");
            }
    
            if (! empty($source)) {
                if (! isset($source[$key]))
                    throw new TokenException("Missing Token");
                if (! $this->get($this->id . $source[$key])) {
                    throw new TokenException("Invalid Token");
                }
            }
        }
    
        public function get($key) {
            return $this->db->get($key);
        }
    
        public function remove($key) {
            return $this->db->delete($key);
        }
    
        public function generate($time = 120) {
            $key = hash("sha512", mt_rand(0, mt_getrandmax()));
            $this->db->set($this->id . $key, 1, 0, $time);
            return $key;
        }
    }
    class TokenException extends InvalidArgumentException {
    }
    

    注意:请注意,该示例可能会影响“返回”按钮或刷新,因为令牌将在 120 秒后自动删除,这可能会影响用户友好功能

    【讨论】:

    • -1 您的方法仍然存在漏洞。您没有“绑定”到原始用户的 IP 或会话。攻击者可以实时获取他的令牌并将其用于攻击。您还可以使用散列一个数字生成令牌 - 这可能是暴力破解。
    • 我不同意扎菲的观点。如今,将会话绑定到 IP 并不是一种用户友好的方式,因为许多用户使用具有不断变化的 ip 的移动设备。此外,如果有人能够读取某人的 cookie 或网络流,那么您遇到的问题比这更糟。
    • @DanielM 我在 IP or 会话上编写了绑定,但他从未将令牌分配给既没有 ip 也没有会话的特定用户,因此攻击者不需要知道您的 cookie,因为他可以使用他的令牌作为你的。
    • 假设在进入这个阶段之前,您将有一个有效的会话身份验证过程.. 永远不会少有建设性的批评.. 如果$id 使用更新类.. 谢谢跨度>
    • 1) TokenException 不是 InvalidArgumentException! 2) DB 不是 memcache
    【解决方案5】:

    你能否参考以下网站,这可能会得到一些想法。

    1.) https://docs.djangoproject.com/en/dev/ref/contrib/csrf/

    2.) http://blog.whitehatsec.com/tag/session-token/

    感谢回复。

    【讨论】:

      【解决方案6】:

      我已经在另一个论坛上回答了类似的问题:here。希望这会有所帮助。它解释了 CSRF 预防的基本过程,并链接了一些 CSRF 框架的代码。

      如果您想要更高的安全性,请在每个会话的每个请求后更改令牌。如果您想要更好的可用性,请在每个会话中保留一个令牌。

      【讨论】:

        猜你喜欢
        • 2019-11-13
        • 2015-08-17
        • 2023-02-15
        • 2018-10-29
        • 2010-10-05
        • 2014-07-17
        • 2018-04-17
        • 2018-03-09
        • 2020-08-23
        相关资源
        最近更新 更多