为了账号安全,请及时绑定邮箱和手机立即绑定

“让我登录”-最好的方法

/ 猿问

“让我登录”-最好的方法

慕哥6287543 2019-07-01 11:07:24

“让我登录”-最好的方法

我的Web应用程序使用会话来存储用户登录后的信息,并在应用程序中从一页到另一页时维护这些信息。在这个特定的应用程序中,我存储user_idfirst_namelast_name那个人。

我想在登录时提供一个“让我登录”的选项,这个选项将在用户的机器上放置一个cookie,为期两周,当用户返回应用程序时,将使用相同的详细信息重新启动他们的会话。

做这件事的最佳方法是什么?我不想把他们的user_id在cookie中,这似乎使一个用户很容易尝试伪造另一个用户的身份。


查看完整描述

3 回答

?
DIEA

好的,让我直截了当地说:如果你把用户数据,或者从用户数据中派生出来的任何东西放入一个Cookie中,那么你就做错了什么。

那里。我说过了。现在我们可以继续讨论实际的答案了。

你问哈希用户数据有什么问题吗?它可以归结为暴露表面和通过默默无闻的安全性。

想象一下你是个攻击者。在您的会话中,您会看到为“记忆-我”设置的加密cookie。宽32个字符。哎呀。那可能是MD5.。

让我们想象一下,他们知道你使用的算法。例如:

md5(salt+username+ip+salt)

现在,攻击者所需要做的就是用暴力强制使用“SALT”(实际上不是SALT,但稍后会更详细地介绍),现在他可以用任何用户名为他的IP地址生成所有他想要的假令牌!但硬汉-强迫吃盐是很难的,对吧?绝对一点儿没错。但是现在的GPU非常擅长它。除非你在里面使用足够多的随机性(让它足够大),否则它会迅速下降,并随之成为你城堡的钥匙。

简而言之,唯一能保护你的就是盐,它并不像你想的那样保护你。

但是等等!

所有这些都是预测攻击者知道算法!如果这是秘密和混乱,那么你是安全的,对吗?不对..这种思维方式有一个名字:默默无闻,这应该是绝不可能值得信赖。

更好的方法

更好的方法是永远不让用户的信息离开服务器,除了id。

当用户登录时,生成一个大的(128到256位)随机令牌。将其添加到将令牌映射到UserID的数据库表中,然后将其发送到cookie中的客户端。

如果攻击者猜测另一个用户的随机令牌怎么办?

我们来算一算吧。我们正在生成128位随机令牌。这意味着:

possibilities = 2^128possibilities = 3.4 * 10^38

现在,为了展示这个数字有多大,让我们想象一下,互联网上的每台服务器(比如今天的5000万台)都试图以每秒10亿美元的速度强迫这个数字。在现实中,您的服务器会在这样的负载下融化,但让我们来讨论一下。

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000guesses_per_second = 50,000,000,000,000,000

所以每秒有50千兆次的猜测。太快了!对吗?

time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000time_to_guess = 6,800,000,000,000,000,000,000

六点八秒.。

让我们试着把它降到更友好的数字。

215,626,585,489,599 years

甚至更好:

47917 times the age of the universe

是的,那是宇宙年龄的47917倍.

基本上,它不会被破解。

因此,总括而言:

我推荐的更好的方法是用三个部分存储cookie。

function onLogin($user) {
    $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
    storeTokenForUser($user, $token);
    $cookie = $user . ':' . $token;
    $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
    $cookie .= ':' . $mac;
    setcookie('rememberme', $cookie);}

然后,验证:

function rememberMe() {
    $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
    if ($cookie) {
        list ($user, $token, $mac) = explode(':', $cookie);
        if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
            return false;
        }
        $usertoken = fetchTokenByUserName($user);
        if (hash_equals($usertoken, $token)) {
            logUserIn($user);
        }
    }}

注意:不要使用令牌或用户和令牌的组合来查找数据库中的记录。始终确保获取基于用户的记录,然后使用定时安全比较函数来比较获取的令牌。更多关于定时攻击的信息.

现在,它是非常重要的是SECRET_KEY是一个密码秘密(由类似的东西生成)/dev/urandom和/或从高熵输入导出)。还有,GenerateRandomToken()必须是强随机源(mt_rand()还不够强大。使用库,例如兰多姆利布随机复合,或mcrypt_create_iv()带着DEV_URANDOM)...

这个hash_equals()是为了防止定时攻击..如果在PHP5.6下面使用PHP版本,则函数hash_equals()不支持。在这种情况下,您可以替换hash_equals()使用timingSafeCompare函数:

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;}


查看完整回答
反对 回复 2019-07-01
?
精慕HU

通常我会做这样的事情:

  1. 用户登录“让我登录”
  2. 创建会话
  3. 创建一个名为“包含的东西”的cookie:MD5(SALT+用户名+IP+SALT)和一个名为SomethingElse(包含id)
  4. 将cookie存储在数据库中
  5. 用户做东西和离开
  6. 用户返回,检查某些内容-Else cookie,如果存在,则从数据库中获取该用户的旧哈希;检查cookie的内容-与数据库中的哈希匹配,这也应该与新计算的散列(对于IP)匹配-例如:cookieHash==databaseHash==md5(salt+username+ip+salt),(如果它们存在),goto 2,如果它们不去1

当然,您可以使用不同的cookie名称等,也可以稍微更改cookie的内容,只需确保它不容易创建。例如,您还可以在创建用户时创建USER_SALT,并将其放入cookie中。

此外,您也可以使用SHA 1而不是MD5(或几乎任何算法)。


查看完整回答
反对 回复 2019-07-01
?
慕侠2389804

导言

你的头衔“让我登录”-最好的方法让我很难知道从哪里开始,因为如果您正在寻找最佳的方法,那么您必须考虑以下几点:

  • 鉴定
  • 保安

曲奇饼

Cookie是脆弱的,在常见的浏览器cookie窃取漏洞和跨站点脚本攻击之间,我们必须接受Cookie是不安全的。要帮助提高安全性,您必须注意php setcookies具有其他功能,如

布尔赛特曲奇(字符串$name[,String$value],int$end=0[,String$path],String$Domain[,bool]$安全=假[.]$httponly=[假])

  • 安全(使用HTTPS连接)
  • httponly(通过XSS攻击减少身份盗用)

定义

  • 令牌(n长的不可预测的随机字符串,如。/dev/urandom)
  • 引用(n长不可预测的随机字符串,例如。/dev/urandom)
  • 签名(使用HMAC方法生成键控哈希值)

简单方法

一个简单的解决办法是:

  • 用户登录时请记住我
  • 带有令牌和签名的登录Cookie
  • 返回时,将检查签名。
  • 如果签名没问题.。然后在数据库中查找用户名和令牌。
  • 如果无效.。返回到登录页面
  • 如果有效,自动登录

上面的案例研究总结了本页给出的所有示例,但它们的缺点是

  • 没有办法知道饼干是不是被偷了
  • 攻击者可以访问敏感操作,如更改密码或数据,如个人和烘烤信息等。
  • 受影响的cookie在cookie生命周期中仍然有效。

更好的解决方案

更好的解决办法是

  • 用户登录,并选择“记住我”。
  • 生成令牌&签名并存储在cookie中
  • 这些令牌是随机的,只适用于单一的修改。
  • 每次访问该站点时,将替换令牌。
  • 当未登录的用户访问站点时,将验证签名、令牌和用户名。
  • 请记住,我登录应该有限的访问,不允许修改密码,个人信息等。

示例代码

// Set privateKey// This should be saved securely $key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';$key = pack("H*", $key); // They key is used in binary form// Am Using Memecahe as Sample Database$db = new Memcache();$db->addserver("127.0.0.1");try {
    // Start Remember Me
    $rememberMe = new RememberMe($key);
    $rememberMe->setDB($db); // set example database

    // Check if remember me is present
    if ($data = $rememberMe->auth()) {
        printf("Returning User %s\n", $data['user']);

        // Limit Acces Level
        // Disable Change of password and private information etc

    } else {
        // Sample user
        $user = "baba";

        // Do normal login
        $rememberMe->remember($user);
        printf("New Account %s\n", $user);
    }} catch (Exception $e) {
    printf("#Error  %s\n", $e->getMessage());}

使用的类

class RememberMe {
    private $key = null;
    private $db;

    function __construct($privatekey) {
        $this->key = $privatekey;
    }

    public function setDB($db) {
        $this->db = $db;
    }

    public function auth() {

        // Check if remeber me cookie is present
        if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
            return false;
        }

        // Decode cookie value
        if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
            return false;
        }

        // Check all parameters
        if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
            return false;
        }

        $var = $cookie['user'] . $cookie['token'];

        // Check Signature
        if (! $this->verify($var, $cookie['signature'])) {
            throw new Exception("Cokies has been tampared with");
        }

        // Check Database
        $info = $this->db->get($cookie['user']);
        if (! $info) {
            return false; // User must have deleted accout
        }

        // Check User Data
        if (! $info = json_decode($info, true)) {
            throw new Exception("User Data corrupted");
        }

        // Verify Token
        if ($info['token'] !== $cookie['token']) {
            throw new Exception("System Hijacked or User use another browser");
        }

        /**
         * Important
         * To make sure the cookie is always change
         * reset the Token information
         */

        $this->remember($info['user']);
        return $info;
    }

    public function remember($user) {
        $cookie = [
                "user" => $user,
                "token" => $this->getRand(64),
                "signature" => null
        ];
        $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
        $encoded = json_encode($cookie);

        // Add User to database
        $this->db->set($user, $encoded);

        /**
         * Set Cookies
         * In production enviroment Use
         * setcookie("auto", $encoded, time() + $expiration, "/~root/",
         * "example.com", 1, 1);
         */
        setcookie("auto", $encoded); // Sample
    }

    public function verify($data, $hash) {
        $rand = substr($hash, 0, 4);
        return $this->hash($data, $rand) === $hash;
    }

    private function hash($value, $rand = null) {
        $rand = $rand === null ? $this->getRand(4) : $rand;
        return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true));
    }

    private function getRand($length) {
        switch (true) {
            case function_exists("mcrypt_create_iv") :
                $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
                break;
            case function_exists("openssl_random_pseudo_bytes") :
                $r = openssl_random_pseudo_bytes($length);
                break;
            case is_readable('/dev/urandom') : // deceze
                $r = file_get_contents('/dev/urandom', false, null, 0, $length);
                break;
            default :
                $i = 0;
                $r = "";
                while($i ++ < $length) {
                    $r .= chr(mt_rand(0, 255));
                }
                break;
        }
        return substr(bin2hex($r), 0, $length);
    }}

Firefox和Chrome的测试

enter image description here

优势

  • 更好的安全
  • 攻击者的有限访问权限
  • 当cookie被窃取时,它仅对单一访问有效。
  • 当下一次原始用户访问该站点时,您可以自动检测并通知用户盗窃案。

劣势

  • 不支持通过多个浏览器(Mobile&Web)进行持久连接
  • Cookie仍然可以被窃取,因为用户只有在下一次登录后才会收到通知。

快速修正

  • 对每个必须具有持久连接的系统引入审批系统
  • 使用多个cookie进行身份验证

多重Cookie方法

当攻击者准备窃取cookie时,将其唯一的焦点放在特定的网站或域上,例如。example.com

但实际上您可以对来自两个不同域的用户进行身份验证(example.com & fakeaddsite.com)并使它看起来像“广告饼干”

  • 用户登录到

    example.com

    记住我
  • 在cookie中存储用户名、令牌、引用
  • 在数据库中存储用户名、标记、引用。模缓存
  • 通过get和iframe发送参考ID到

    fakeaddsite.com

  • com使用引用从数据库中获取用户和令牌
  • com存储签名
  • 当用户返回使用iframe从fakeaddsite.com获取签名信息时
  • 将其数据组合起来并进行验证。
  • ..你知道剩下的

有些人可能会想,你怎么能用两种不同的曲奇呢?嗯,这是可能的,想象一下example.com = localhostfakeaddsite.com = 192.168.1.120..如果你检查一下饼干,它会像这样

enter image description here

从上面的图像

  • 当前访问的站点是本地主机。
  • 它还包含192.168.1.120设置的cookie。

192.168.1.120

  • 只接受定义

    HTTP_REFERER

  • 只接受来自指定的连接。

    REMOTE_ADDR

  • 没有JavaScript,没有内容,但不包含任何内容,而只是对信息进行签名,并从cookie中添加或检索信息

优势

  • 99%的时间你欺骗了攻击者
  • 您可以轻松地锁定攻击者首次尝试的帐户。
  • 即使在下一次登录之前,也可以像其他方法一样防止攻击。

劣势

  • 单次登录对服务器的多个请求

改进

  • 已使用

    ajax


查看完整回答
反对 回复 2019-07-01

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信