web-dev-qa-db-ja.com

「ログイン状態を保持」-最適なアプローチ

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

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

これを行うための最良のアプローチは何ですか? Cookieにuser_idを保存したくないのは、あるユーザーが別のユーザーのIDを簡単に偽造しようとするからです。

238
Matthew

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

そこ。言った。これで、実際の答えに移ることができます。

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

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

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

md5(salt+username+ip+salt)

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

要するに、あなたを保護しているのは塩だけです。それはあなたが思っているほどあなたを本当に保護しているわけではありません。

しかし、待って!

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

より良い方法

より良い方法は、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

毎秒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);
        }
    }
}

注:データベース内のレコードを検索するために、トークンまたはユーザーとトークンの組み合わせを使用しないでください。必ずユーザーに基づいてレコードをフェッチし、タイミングセーフな比較関数を使用して、フェッチしたトークンを後で比較してください。 タイミング攻撃の詳細

現在、veryは、SECRET_KEYが暗号の秘密であることが重要です(/dev/urandomのようなものによって生成され、および/または高エントロピー入力から派生)。また、GenerateRandomToken()は強力なランダムソースである必要があります(mt_Rand()は十分に強力ではありません。 RandomLibrandom_compat 、またはDEV_URANDOMを使用したmcrypt_create_iv()などのライブラリを使用してください)...

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;
}
688
ircmaxell

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

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

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

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

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

94
Pim Jager

はじめに

あなたの肩書き「ログイン状態を保持」-最善のアプローチで、どこから始めればよいかわかりにくくなります。以下を考慮する必要があります。

  • 識別
  • セキュリティ

Cookies

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

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

  • セキュア(HTTPS接続を使用)
  • httponly(XSS攻撃により個人情報の盗難を削減)

定義

  • トークン(長さが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でのテスト

enter image description here

利点

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

欠点

  • 複数のブラウザー(モバイル&Web)を介した持続的接続はサポートしていません
  • ユーザーは次回のログイン後にのみ通知を受け取るため、Cookieは引き続き盗まれます。

クイックフィックス

  • 持続的な接続が必要な各システムの承認システムの導入
  • 認証に複数のCookieを使用する

複数のCookieアプローチ

攻撃者がCookieを盗もうとしている場合、特定のWebサイトまたはドメインにのみフォーカスします。 example.com

しかし実際には、2つの異なるドメイン(example.comfakeaddsite.com)そして、「広告Cookie」のように見せます

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

一部の人々は、2つの異なるCookieをどのように使用できるのかと疑問に思うかもしれません。可能です。example.com = localhostfakeaddsite.com = 192.168.1.120を想像してください。 Cookieを検査すると、次のようになります

enter image description here

上の画像から

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

192.168.1.120

  • 定義済みのHTTP_REFERERのみを受け入れます
  • 指定されたREMOTE_ADDRからの接続のみを受け入れます
  • JavaScriptもコンテンツもありませんが、情報に署名してCookieから追加または取得するだけで構成されます

利点

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

欠点

  • 1回のログインでサーバーへの複数リクエスト

改善

  • Iframe use ajaxを使用しました
74
Baba

2つの非常に興味深い記事がありますが、「記憶」問題の完璧な解決策を探しているときに見つけました。

24
Stefan Gehrig

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

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

6
Dan Rosenstark

Stefanが言及したアプローチを推奨します(つまり、 改善された永続的ログインCookieベストプラクティス のガイドラインに従います)。また、Cookieが HttpOnly cookies アクセスできない、潜在的に悪意のあるJavaScript。

5
Walter Rumsby

古いスレッドですが、依然として有効な懸念事項です。セキュリティに関するいくつかの良い反応に気づき、「隠蔽によるセキュリティ」の使用を避けましたが、実際の技術的手法は私の目には不十分でした。メソッドを提供する前に言わなければならないこと:

  • NEVERパスワードをクリアテキストで保存する... EVER!
  • NEVERユーザーのハッシュされたパスワードをデータベースの複数の場所に保存します。サーバーバックエンドは、ユーザーテーブルからハッシュ化されたパスワードを常に引き出すことができます。 DBトランザクションを追加する代わりに冗長データを保存するのは効率的ではありません。逆の場合も同様です。
  • セッションIDは一意である必要があるため、2人のユーザーがeverを共有することはできません。したがって、IDの目的(運転免許証ID番号は他の人と一致しますか?いいえ)これにより、2つの一意の文字列に基づいて、2ピースの一意の組み合わせが生成されます。 Sessionsテーブルでは、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で一致を取得します。
  • サーバーは、GoogleにログインするためのAJAXリクエストを実行するログインページに攻撃者を送信します。
  • 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アドレスはその性質上、UNIQUEであると想定されているため、各エントリに一意の値があることは理にかなっています。一方、ホスト名は別々のネットワーク上で正当に複製される可能性があります。 「Home-PC」をコンピューター名の1つとして使用する人は何人いますか?ユーザー名はサーバーバックエンドによってセッションデータから取得されるため、操作することはできません。トークンに関しては、ページのセッショントークンを生成する同じ方法を使用して、ユーザーの自動サインイン用のCookieでトークンを生成する必要があります。最後に、ユーザーが資格情報を再検証する必要がある場合に備えて、日時コードが追加されます。ユーザーのログイン時にこの日時を更新して数日以内に保持するか、最後のログインが1か月程度しか保持しない場合でも、設計に応じて強制的に期限切れにします。

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

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

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

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

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

4
user253780

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

4

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

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

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

  2. ログインに成功したら(Remember Meにチェックマークを付けて):

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

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

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

    d)「Remember Me」テーブルで、UserID、IP Address、bigUserID、bigKeyを含むレコードを追加します

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

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

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

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

私が見ることができる唯一の脆弱性は次のとおりです。

  • 誰かのラップトップを手に入れ、CookieでIPアドレスを偽装することができます。
  • 毎回異なるIPアドレスをスプーフィングし、全体を推測できますが、2つの大きな文字列を一致させると、上記と同様の計算を行うことになります。
2
Enigma Plus

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

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

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

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

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

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

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

2
trante

私はすべての答えを読みましたが、それでも自分がすべきことを抽出するのが難しいと感じました。写真が1kワードに相当する場合、他の人がBarry Jaspanの Improved Persistent Login Cookie Best Practice に基づいて安全な永続ストレージを実装するのに役立つことを願っています

enter image description here

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

2
Josh Woodcock

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

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

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

0
staticsan