「ログイン状態を維持」-最善のアプローチ


257

私のWebアプリケーションはセッションを使用して、ユーザーがログインした後のユーザーに関する情報を保存し、アプリ内のページ間を移動するときにその情報を維持します。この特定のアプリケーションでは、私が保管していますuser_idfirst_nameそしてlast_name人の。

ログイン時に[ログイン状態を維持する]オプションを提供して、ユーザーのマシンにCookieを2週間保持し、ユーザーがアプリに戻ったときに同じ詳細でセッションを再開します。

これを行うための最良のアプローチは何ですか?user_idあるユーザーが別のユーザーのIDを偽造して簡単に偽造できるように思われるため、Cookieにそれらを保存したくありません。

回答:


735

わかりました、率直に言ってみましょう。ユーザーデータ、またはユーザーデータから派生したものをこの目的のためにCookieに入れている場合は、何か間違っています。

そこ。私はそれを言った。それでは、実際の答えに移りましょう。

ユーザーデータのハッシュの何が問題になっていますか?まあ、それは露出面とあいまいさによるセキュリティに帰着します。

あなたが攻撃者だと一瞬想像してみてください。セッションで、remember-meに設定された暗号化Cookieが表示されます。幅は32文字です。ああ。それはMD5かもしれません...

彼らがあなたが使ったアルゴリズムを知っていることを一瞬想像してみましょう。例えば:

md5(salt+username+ip+salt)

これで、攻撃者が行う必要があるのは、「塩」をブルートフォースすることです(これは実際には塩ではありませんが、後で詳しく説明します)。IPアドレスのユーザー名を使用して、必要なすべての偽のトークンを生成できます。しかし、ブルートフォースをかけるのは難しいですよね?もちろんです。しかし、現代のGPUは非常に優れています。そして、その中で十分なランダム性を使用しない限り(十分に大きくする)、それはすぐに落下し、それに伴って城の鍵となります。

要するに、あなたを守る唯一のものはあなたが思っているほど実際にあなたを守っていない塩です。

ちょっと待って!

これらはすべて、攻撃者がアルゴリズムを知っていることを前提としています!それが秘密で混乱しているなら、あなたは安全ですよね?間違っています。その考え方には名前があります:Security Through Obscurity。これは決して信頼されるべきではありません。

より良い方法

より良い方法は、IDを除いて、ユーザーの情報がサーバーから出ないようにすることです。

ユーザーがログインするときに、大きな(128〜256ビット)ランダムトークンを生成します。これを、トークンをユーザーIDにマップするデータベーステーブルに追加し、Cookieでクライアントに送信します。

攻撃者が別のユーザーのランダムトークンを推測した場合はどうなりますか?

さて、ここで数学をしましょう。128ビットのランダムトークンを生成しています。つまり、

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

さて、その数がいかに驚くほど大きいかを示すために、インターネット上のすべてのサーバー(今日は50,000,000としましょう)がその数を毎秒1,000,000,000の割合でブルートフォースにしようとしているとしましょう。実際には、サーバーはそのような負荷の下で溶けるでしょうが、これを試してみましょう。

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

つまり、1秒あたり50兆回の推測です。それは速いです!正しい?

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

6.8セクシオン秒...

それをもっと親しみやすい数字にしてみましょう。

215,626,585,489,599 years

またはさらに良い:

47917 times the age of the universe

はい、それは宇宙の時代の47917倍です...

基本的に、それはクラックされないでしょう。

要約すると:

私が推奨するより良いアプローチは、3つの部分で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()十分に強力ではありません。RandomLibrandom_compatなどのライブラリを使用するかmcrypt_create_iv()DEV_URANDOM)で...

hash_equals()ないようにすることであるタイミング攻撃を。PHP 5.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;
}

7
しかし、このアプローチは、誰でもこのユーザー名とCookieを取得し、他のデバイスからこのユーザーとしてログインできることを意味しているのではないですか?
2013年

8
笑:-)、47917年が推測する最大時間であることに注意してください。ランダムトークンも1時間で推測できます。
storm_buster 2014

33
あなたのコードがあなたの答えと矛盾するので、それは奇妙です。「ユーザーデータをCookieに入れている場合[...]何か間違っている」と言いますが、それはまさにあなたのコードがしていることです!Cookieからユーザー名を削除し、トークンのみでハッシュを計算し(Cookieの盗難を防ぐためにIPアドレスを追加することもできます)、rememberMe()でfetchTokenByUserNameの代わりにfetchUsernameByTokenを実行する方がいいのではないですか?
Leven

9
PHP 5.6以降、hash_equalsを使用して、文字列比較を行う際のタイミング攻撃を防ぐことができます。
F21 2014年

5
@Levitは、誰かが有効なトークンを取得し、それに関連付けられているユーザーIDを変更するのを防ぎます。
ircmaxell 2014

93

セキュリティに関する通知:確定的データのMD5ハッシュに基づいてCookieを作成することはお勧めできません。CSPRNGから派生したランダムトークンを使用することをお勧めします。より安全なアプローチについては、この質問に対するircmaxellの回答を参照してください。

通常、私はこのようなことをします:

  1. ユーザーは「ログイン状態を維持」でログインします
  2. セッションを作成
  3. SOMETHINGと呼ばれるcookieを作成します:md5(salt + username + ip + salt)と、somethingElseと呼ばれるIDを含むCookie
  4. データベースにCookieを保存する
  5. ユーザーは物事をし、去ります----
  6. ユーザーが戻って、somethingElse Cookieが存在する場合はそれを確認し、そのユーザーのデータベースから古いハッシュを取得し、Cookieの内容を確認します。データベースのハッシュとSOMETHINGが一致することを確認します。 ip)したがって、cookieHash == databaseHash == md5(salt + username + ip + salt)の場合、2に移動します。1に移動しない場合

もちろん、異なるCookie名などを使用することもできます。また、Cookieの内容を少し変更することもできます。簡単に作成されないようにしてください。たとえば、ユーザーの作成時にuser_saltを作成し、それをCookieに置くこともできます。

また、md5(またはほとんどすべてのアルゴリズム)の代わりにsha1を使用することもできます。


30
なぜハッシュにIPを含めるのですか?また、Cookieにタイムスタンプ情報が含まれていることを確認し、この情報を使用してCookieの最大経過時間を設定します。これにより、永遠に適したIDトークンが作成されなくなります。
スコットミッチェル

4
@Abhishek Dilliwal:これはかなり古いスレッドですが、私はMathewと同じ答えを探してそれに出くわしました。session_IDはすべてのsession_start();を変更するため、dbハッシュ、Cookieハッシュ、および現在のsession_IDを確認できないため、session_IDを使用してもPimの回答には効果がないと思います。私がこれを指摘すると思いました。
Partack 2011

3
鈍くて申し訳ありませんが、2番目のCookieの何か他の目的は何ですか?この場合のIDは何ですか?ユーザーがログイン状態を維持する機能を使用したいかどうかを示すのは、単なる「true / false」値のようなものですか?もしそうなら、なぜクッキーSOMETHINGが最初に存在するかどうかを確認してみませんか?ユーザーがログインを持続させたくない場合、SOMETHING Cookieはそもそも存在しませんか?最後に、ハッシュを動的に再生成し、追加のセキュリティ対策としてCookieおよびDBと照合しますか?
itsmequinn

4
トークンはランダムである必要があり、ユーザー/彼のIP /彼のユーザーエージェント/何にも接続されていません。これは主要なセキュリティ上の欠陥です。
パミール2013年

4
なぜ2つの塩を使用するのですか?md5(salt + username + ip + salt)
アーロンクレイダー2013

77

前書き

あなたのタイトル「Keep Me Logged In」-最善のアプローチでは、どこから始めればよいかわからなくなります。最善のアプローチを検討している場合、次のことを考慮する必要があるためです。

  • 識別
  • 安全保障

クッキー

Cookieは脆弱です。一般的なブラウザーのCookie盗難の脆弱性とクロスサイトスクリプティング攻撃の間で、Cookieは安全ではないことを受け入れる必要があります。セキュリティを向上させるにphp setcookiesは、次のような追加機能があることに注意する必要があります

bool setcookie(string $ name [、string $ value [、int $ expire = 0 [、string $ path [、string $ domain [、bool $ secure = false [、bool $ httponly = false]]]]]])

  • セキュア(HTTPS接続を使用)
  • httponly(XSS攻撃によるIDの盗難を減らす)

定義

  • トークン(長さがnの予測できないランダム文字列。例:/ dev / urandom)
  • 参照(長さがnの予測不能なランダム文字列。例:/ dev / urandom)
  • 署名(HMACメソッドを使用してキー付きハッシュ値を生成する)

シンプルなアプローチ

簡単な解決策は次のとおりです。

  • ユーザーは、Remember Meでログオンしています
  • トークンと署名付きで発行されたログイン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でのテスト

ここに画像の説明を入力してください

利点

  • より良いセキュリティ
  • 攻撃者のアクセス制限
  • Cookieが盗まれた場合、単一のアクセスに対してのみ有効です
  • 次に元のユーザーがサイトにアクセスすると、自動的に検出してユーザーに盗難を通知できます

不利益

  • 複数のブラウザー(モバイルおよびWeb)を介した永続的な接続をサポートしていません
  • ユーザーは次のログイン後にのみ通知を受け取るため、Cookieは引き続き盗まれる可能性があります。

クイックフィックス

  • 常時接続が必要なシステムごとに承認システムを導入
  • 認証に複数のCookieを使用する

複数のCookieアプローチ

攻撃者がCookieを盗もうとするとき、特定のWebサイトやドメインなどにのみそれを集中させます。example.com

しかし、実際には2つの異なるドメイン(example.comfakeaddsite.com)のユーザーを認証して、「広告Cookie」のようにすることができます。

  • 私を覚えて、example.comにログオンしたユーザー
  • ユーザー名、トークン、参照をCookieに保存します
  • ユーザー名、トークン、参照をデータベースに保存します。Memcache
  • getおよびiframeを介してfrefaddsite.comに屈折IDを送信します
  • fakeaddsite.comは、参照を使用してデータベースからユーザーとトークンを取得します
  • fakeaddsite.comは署名を保存します
  • ユーザーがfakeaddsite.comからiframeを使用してフェッチ署名情報を返す場合
  • データを組み合わせて検証を行う
  • .....あなたは残りを知っています

一部の人々は、2つの異なるCookieをどのように使用できるのか疑問に思うかもしれません。まあ想像し、その可能性example.com = localhostfakeaddsite.com = 192.168.1.120。クッキーを調べると次のようになります

ここに画像の説明を入力してください

上の画像から

  • 現在アクセスしているサイトはlocalhostです
  • また、192.168.1.120から設定されたCookieも含まれています

192.168.1.120

  • 定義済みのみを受け入れます HTTP_REFERER
  • 指定したものからの接続のみを受け入れる REMOTE_ADDR
  • JavaScriptなし、コンテンツなし、ただし情報に署名してcookieに情報を追加または取得するだけ

利点

  • 攻撃者をだます時間の99%パーセント
  • 攻撃者の最初の試みでアカウントを簡単にロックできます
  • 他の方法と同様に、次回のログイン前でも攻撃を防ぐことができます

不利益

  • 単一のログインのためだけのサーバーへの複数の要求

改善

  • 使用済みiframe使用 ajax

5
@ircmaxellは理論を非常によく説明していますが、ユーザーIDを保存する必要がなく(これは望ましくない情報開示となります)優れた方法で動作し、ユーザーIDとハッシュだけでなく、ブラウザなどのユーザー。これにより、攻撃者が盗んだCookieを利用することがさらに困難になります。これは、これまで見てきた中で最も安全な方法です。+1
MarcelloMönkemeyer2015

6

私はここでこの質問の1つの角度を尋ねました、そしてその答えはあなたが必要とするすべてのトークンベースのタイムアウトCookieリンクにあなたを導くでしょう。

基本的に、ユーザーIDをCookieに保存しません。ユーザーが古いログインセッションを取得するために使用するワンタイムトークン(巨大な文字列)を保存します。次に、それを本当に安全にするために、重い操作(パスワード自体の変更など)のためにパスワードを要求します。


6

古いスレッドですが、まだ問題があります。私はセキュリティに関していくつかの良い反応に気づき、「あいまいさによるセキュリティ」の使用を避けましたが、与えられた実際の技術的方法は私の目には十分ではありませんでした。私の方法を提供する前に私が言わなければならないこと:

  • NEVER EVER ...クリアテキストでパスワードを保存していません!
  • 決してあなたのデータベース内の複数の場所で、ユーザーのハッシュされたパスワードを格納しません。サーバーのバックエンドは常にハッシュされたパスワードをユーザーテーブルから取得できます。追加のDBトランザクションの代わりに冗長データを保存する方が効率的ではありません。逆も同様です。
  • 2人のユーザができるよう、セッションIDのは、一意である必要があり、これまで、IDの故に目的(運転免許証のID番号が今まで別の人物と一致する可能性?いいえ)これは2に基づいて、ツーピースユニークな組み合わせを生成するIDを共有していませんユニークな文字列。セッションテーブルでは、IDをPKとして使用する必要があります。自動サインインで複数のデバイスを信頼できるようにするには、すべての検証済みデバイスのリストを含む信頼済みデバイス用の別のテーブルを使用し(以下の私の例を参照)、ユーザー名を使用してマッピングされます。
  • 既知のデータをCookieにハッシュする目的はありません。Cookieをコピーできます。私たちが探しているのは、攻撃者がユーザーのマシンを危険にさらすことなく取得できない本物の情報を提供する準拠ユーザーデバイスです(ここでも、私の例を参照してください)。ただし、これは、自分のマシンの静的情報(つまり、MACアドレス、デバイスのホスト名、ブラウザによって制限されている場合はユーザーエージェントなど)が一貫性を保つこと(またはそもそもそれを偽装すること)を禁止する正当なユーザーが、この機能を使用します。ただし、これが問題になる場合は、自分を一意に識別するユーザーに自動サインインを提供していることを考慮してください。、したがって、MACのスプーフィング、ユーザーエージェントのスプーフィング、ホスト名のスプーフィング/変更、プロキシの背後への隠蔽などにより、彼らが知られることを拒否した場合、それらは識別できず、自動サービスに対して認証されるべきではありません。これが必要な場合は、使用されているデバイスのIDを確立するクライアント側ソフトウェアにバンドルされているスマートカードアクセスを調べる必要があります。

つまり、システムに自動サインインするには2つの方法があります。

まず、それをすべて他の人に任せる安価で簡単な方法。たとえば、Google +アカウントを使用してサイトサポートのログインを作成した場合、ユーザーが既にGoogleにサインインしている場合にユーザーをログインするための合理化されたgoogle +ボタンがあるはずです(私はいつもここにいるので、この質問に答えるためにここにグーグルにサインイン)。ユーザーが信頼できるサポートされている認証システムで既にサインインしていて、そのためのチェックボックスをオンにした場合、ユーザーが自動的にサインインするようにするには、ロードする前にクライアント側のスクリプトに対応する「サインイン」ボタンの背後にあるコードを実行させる、ユーザーに使用するユーザー名、セッションID、認証システムを含む自動サインインテーブルに一意のIDをサーバーに保存するようにしてください。これらのサインインメソッドはAJAXを使用しているため、とにかく応答を待っています。その応答は、検証済みの応答または拒否のいずれかです。検証された応答を受け取った場合は、それを通常どおりに使用してから、ログインしたユーザーを通常どおりにロードし続けます。それ以外の場合、ログインは失敗しましたが、ユーザーに通知せず、ログインしていないとして続行すると、ユーザーに通知されます。これは、Cookieを盗んだ(または特権を昇格させるために偽造した)攻撃者が、ユーザーがサイトに自動サインインしたことを知らないようにするためです。

これは安価であり、すでにサインインしている可能性のあるユーザーを、GoogleやFacebookなどの場所で、通知することなく検証しようとするため、ダーティだと見なされることもあります。ただし、サイトの自動サインインを要求していないユーザーには使用しないでください。この特定の方法は、Google +やFBなどの外部認証専用です。

外部認証システムを使用して、ユーザーが検証されたかどうかを裏側のサーバーに通知するため、攻撃者は、それ自体では役に立たない一意のID以外のものを取得できません。詳しく説明します。

  • ユーザー「joe」が初めてサイトにアクセスし、セッションIDがcookie「session」に配置されました。
  • ユーザー「joe」がログインし、特権をエスカレートし、新しいセッションIDを取得し、Cookie「session」を更新します。
  • ユーザー「joe」はgoogle +を使用して自動サインインすることを選択し、Cookie「keepmesignedin」に配置された一意のIDを取得します。
  • ユーザー 'joe'はgoogleにサインインを維持しているため、バックエンドでgoogleを使用してサイトにユーザーを自動サインインさせることができます。
  • 攻撃者は体系的に「keepmesignedin」(これはすべてのユーザーに配布される公開知識です)の一意のIDを試行し、他の場所にはサインインしません。'joe'に与えられた一意のIDを試行します。
  • サーバーは「joe」の一意のIDを受け取り、google +アカウントのDBで一致をプルします。
  • サーバーは、AJAXリクエストを実行するログインページに攻撃者を送信し、Googleにログインを要求します。
  • Googleサーバーはリクエストを受信し、そのAPIを使用して、攻撃者が現在ログインしていないことを確認します。
  • Googleは、この接続で現在ログインしているユーザーがいないという応答を送信します。
  • 攻撃者のページが応答を受信すると、スクリプトは自動的にURLにエンコードされたPOST値でログインページにリダイレクトします。
  • ログインページはPOST値を取得し、「keepmesignedin」のCookieを空の値に送信し、1〜1970の日付まで有効にして自動試行を阻止し、攻撃者のブラウザがCookieを削除するようにします。
  • 攻撃者には通常の初回ログインページが表示されます。

たとえ攻撃者が存在しないIDを使用したとしても、検証された応答が受信された場合を除いて、すべての試行で失敗するはずです。

この方法は、外部認証システムを使用してサイトにサインインする人のために、内部認証システムと組み合わせて使用​​できます。

=========

これで、ユーザーを自動サインインできる独自の認証システムの場合、次のようになります。

DBにはいくつかのテーブルがあります。

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

ユーザー名は255文字まで可能です。サーバープログラムでシステムのユーザー名を32文字に制限していますが、外部認証システムでは@ domain.tldのユーザー名がそれよりも大きい場合があるため、互換性を最大にするためにメールアドレスの最大長をサポートしています。

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

ログインしたときのユーザー名はセッションデータにあり、プログラムはnullデータを許可しないため、このテーブルにはユーザーフィールドがないことに注意してください。session_idとsession_tokenは、ランダムなmd5ハッシュ、sha1 / 128/256ハッシュ、ランダムな文字列が追加されたハッシュされた日時スタンプ、または好きなものを使用して生成できますが、出力のエントロピーは許容できる限り高くする必要がありますブルートフォース攻撃が地面から離れるのを軽減し、セッションクラスによって生成されたすべてのハッシュを追加する前に、セッションテーブルで一致するかどうかを確認する必要があります。

TABLE autologin:
UID - auto increment, PK
username - varchar(255), NOT NULL, allow duplicates
hostname - varchar(255), NOT NULL, allow duplicates
mac_address - char(23), NOT NULL, unique
token - varchar(?), NOT NULL, allow duplicates
expires - datetime code

MACアドレスはその性質上、一意であることが想定されているため、各エントリが一意の値を持つことは理にかなっています。一方、ホスト名は別のネットワーク上で正当に複製される可能性があります。「Home-PC」をコンピュータ名の1つとして使用している人はどれくらいいますか?ユーザー名はサーバーバックエンドによってセッションデータから取得されるため、操作することはできません。トークンについては、ページのセッショントークンを生成する同じ方法を使用して、ユーザーの自動サインイン用のCookieにトークンを生成する必要があります。最後に、ユーザーが資格情報を再検証する必要がある場合の日時コードが追加されます。ユーザーのログイン時にこの日時を更新して数日以内に維持するか、最後のログインに関係なく、強制的に1か月間だけ維持するように設計の指示に従って強制的に期限切れにします。

体系的に彼らが自動兆候を知っているユーザーのためのMACとホスト名をスプーフィングからこれを防止する誰か。NEVERユーザーにパスワード、クリアテキスト、またはその他の方法でCookieを保持してもらいます。セッショントークンの場合と同じように、各ページナビゲーションでトークンを再生成します。これにより、攻撃者が有効なトークンCookieを取得してログインに使用する可能性が大幅に減少します。一部の人々は、攻撃者が被害者からCookieを盗み、セッション再生攻撃を行ってログインする可能性があると述べようとします。攻撃者がCookieを盗む(これが可能である)場合、デバイス全体を危険にさらす可能性があります。つまり、デバイスを使用してログインするだけで、Cookieを盗む目的を完全に失う可能性があります。サイトがHTTPS(パスワード、CC番号、またはその他のログインシステムを処理する場合)で実行されている限り、ブラウザー内で可能なすべての保護をユーザーに提供しています。

注意すべき点の1つは、自動サインインを使用する場合、セッションデータが期限切れにならないようにすることです。セッションを誤って継続する機能を期限切れにすることができますが、システム間で検証すると、セッションデータがセッション間で継続すると予想される永続的なデータである場合は、セッションデータを再開する必要があります。永続セッションデータと非永続セッションデータの両方が必要な場合は、ユーザー名をPKとして永続セッションデータ用の別のテーブルを使用し、通常のセッションデータと同じようにサーバーにデータを取得させます。別の変数を使用します。

この方法でログインが完了すると、サーバーはセッションを検証する必要があります。これは、盗難または侵害されたシステムに対する期待をコード化できる場所です。セッションデータへのログインのパターンやその他の予期される結果は、アクセスを獲得するためにシステムがハイジャックされた、またはCookieが偽造されたという結論につながることがよくあります。これは、ISS Techが、アカウントのロックダウンまたは自動サインインシステムからのユーザーの自動削除をトリガーするルールを設定できる場所であり、ユーザーが攻撃者を成功させる方法とその方法を判断するのに十分な時間、攻撃者を締め出します。

最後に、回復の試み、パスワードの変更、またはログインの失敗がしきい値を超えた場合は、ユーザーが適切に検証してこれが発生したことを確認するまで、自動サインインが無効になることを確認してください。

誰かが私の回答でコードが渡されることを期待していた場合は謝罪します。それはここでは起こりません。私はPHP、jQuery、およびAJAXを使用してサイトを実行し、Windowsをサーバーとして使用することはありません。



4

おそらくあなただけが知っている秘密でハッシュを生成し、それをDBに保存して、ユーザーに関連付けられるようにします。かなりうまくいくはずです。


これは、ユーザーの作成時に作成される一意の識別子ですか、それともユーザーが新しい「ログイン状態を維持する」Cookieを生成するたびに変更されますか?
マシュー

1
Tim Janssonの回答は、ハッシュを生成するための優れたアプローチを説明していますが、パスワードが含まれていない方が安全だと思います
Jani Hartikainen

2

私の解決策はこのようなものです。100%防弾というわけではありませんが、ほとんどの場合であなたを救うと思います。

ユーザーが正常にログインすると、次の情報を含む文字列が作成されます。

$data = (SALT + ":" + hash(User Agent) + ":" + username 
                     + ":" + LoginTimestamp + ":"+ SALT)

暗号化し$data、タイプをHttpOnlyに設定し、Cookieを設定します。

ユーザーがサイトに戻ってきたら、次の手順を実行します。

  1. Cookieデータを取得します。Cookie内の危険な文字を削除します。:キャラクターで爆発させます。
  2. 妥当性を確認します。CookieがX日より古い場合は、ユーザーをログインページにリダイレクトします。
  3. クッキーが古くない場合; データベースから最新のパスワード変更時刻を取得します。ユーザーの最後のログイン後にパスワードが変更された場合、ユーザーをログインページにリダイレクトします。
  4. 最近パスが変更されていない場合; ユーザーの現在のブラウザエージェントを取得します。(currentUserAgentHash == cookieUserAgentHash)かどうかを確認します。エージェントが同じ場合は、次のステップに進み、それ以外の場合はログインページにリダイレクトします。
  5. すべてのステップが成功した場合は、ユーザー名を承認します。

ユーザーがログアウトする場合は、このCookieを削除してください。ユーザーが再ログインする場合は、新しいCookieを作成します。


2

暗号化されたものをハッキングする必要があるのが暗号化されたバージョンである場合、暗号化されたものをCookieに保存するという概念は理解できません。何か足りない場合はコメントしてください。

私はこのアプローチを 'Remember Me'に取り入れることを考えています。問題があればコメントしてください。

  1. 複数のデバイスからログインできるように、ユーザーテーブルとは別に、「Remember Me」データを格納するテーブルを作成します。

  2. ログイン成功時(Remember Meがチェックされている場合):

    a)このマシンのUserIDとして使用される一意のランダム文字列を生成します:bigUserID

    b)一意のランダム文字列を生成します:bigKey

    c)Cookieを保存します:bigUserID:bigKey

    d)[Remember Me]テーブルに、UserID、IPアドレス、bigUserID、bigKeyを含むレコードを追加します

  3. ログインが必要なものにアクセスしようとする場合:

    a)Cookieを確認し、一致するIPアドレスでbigUserIDとbigKeyを検索します

    b)見つかった場合は、ログインしますが、ユーザーテーブルに「ソフトログイン」のフラグを設定して、危険な操作があった場合に完全なログインを要求できるようにします。

  4. ログアウト時に、そのユーザーのすべての「Remember Me」レコードに期限切れのマークを付けます。

私が見ることができる唯一の脆弱性は、

  • あなたは誰かのラップトップを手に入れ、クッキーで彼らのIPアドレスを偽装することができました。
  • 毎回異なるIPアドレスをスプーフィングして全体を推測できますが、2つの大きな文字列を照合すると、上記と同様の計算を実行することになります。

こんにちは、そしてこの答えをありがとう、私はそれが好きです。ただし、1つの質問です。bigUserIDとbigKeyの2つのランダムな文字列を生成する必要があるのはなぜですか。なぜ1だけを生成して使用しないのですか?
Jeremy Belolo、2015年

2
bigKeyは事前定義された時間の後に期限切れになりますが、bigUserIDは期限切れになりません。bigUserIDを使用すると、同じIPアドレスの異なるデバイスで複数のセッションを使用できます。それが理にかなっていると思います-私は少し考えなければなりませんでした:)
Enigma Plus

hmacが役立つことが1つあります。hmacが改ざんされていることがわかった場合、誰かがcookieを盗もうとしたことを確実に知ることができ、すべてのログイン状態をリセットできます。私は正しいですか?
Suraj Jain 2017

2

私はすべての答えを読みましたが、それでも自分のやるべきことを抽出するのは困難でした。写真が1,000ワードに相当する場合、これが他のユーザーがバリージャスパンの改良された永続的ログインCookieのベストプラクティスに基づく安全な永続的ストレージの実装に役立つことを願っています

ここに画像の説明を入力してください

質問、フィードバック、または提案がある場合は、図を更新して、安全な永続的ログインを実装しようとしている初心者に反映させます。


0

「ログイン状態を維持」機能を実装するには、ユーザーにとっての意味を正確に定義する必要があります。最も単純なケースでは、これを使用して、セッションのタイムアウトがはるかに長くなることを意味します。たとえば、2時間ではなく2日です。これを行うには、おそらくデータベース内に独自のセッションストレージが必要になるため、セッションデータのカスタム有効期限を設定できます。次に、ブラウザを閉じたときに期限切れになるのではなく、数日(またはそれ以上)固定されるCookieを設定する必要があります。

「なぜ2日間なのか、なぜ2週間なのか」という質問を聞くことができます。これは、PHPでセッションを使用すると、有効期限が自動的に押し戻されるためです。これは、PHPでのセッションの有効期限が実際にはアイドルタイムアウトであるためです。

そうは言っても、おそらく、セッション自体に格納し、2週間程度で出力する、より難しいタイムアウト値を実装し、それを確認してセッションを強制的に無効にするコードを追加します。または、少なくともログアウトします。これは、ユーザーが定期的にログインするように求められることを意味します。Yahoo! これを行います。


1
サーバーリソースを浪費し、パフォーマンスに悪影響を与えるため、より長いセッションを設定することはおそらく悪いことです
user3091530

0

私はあなたがこれを行うことができると思います:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

お店 $cookiestring DB内とし、クッキーとして設定します。また、そのユーザーのユーザー名をCookieとして設定します。ハッシュの要点は、リバースエンジニアリングできないことです。

ユーザーが現れたら、$cookieString別のCookieからではなく、1つのCookieからユーザー名を取得します。$cookieStringDBに保存されているものと一致する場合、ユーザーは認証されます。password_hashは毎回異なるソルトを使用するため、クリアテキストが何であるかは関係ありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.