Hãy giữ cho tôi đăng nhập vào Mt - cách tiếp cận tốt nhất


257

Ứng dụng web của tôi sử dụng các phiên để lưu trữ thông tin về người dùng sau khi họ đăng nhập và để duy trì thông tin đó khi họ đi từ trang này sang trang khác trong ứng dụng. Trong ứng dụng cụ thể này, tôi đang lưu trữ user_id, first_namelast_namecủa con người.

Tôi muốn cung cấp tùy chọn "Keep Me Logged In" khi đăng nhập sẽ đặt cookie vào máy của người dùng trong hai tuần, sẽ khởi động lại phiên của họ với cùng chi tiết khi họ quay lại ứng dụng.

Cách tiếp cận tốt nhất để làm điều này là gì? Tôi không muốn lưu trữ chúng user_idtrong cookie, vì có vẻ như điều đó sẽ giúp một người dùng dễ dàng thử và giả mạo danh tính của người dùng khác.

Câu trả lời:


735

OK, hãy để tôi nói thẳng điều này: nếu bạn đưa dữ liệu người dùng hoặc bất cứ thứ gì có nguồn gốc từ dữ liệu người dùng vào cookie cho mục đích này, bạn đã làm sai điều gì đó.

Đó Tôi nói rồi. Bây giờ chúng ta có thể chuyển sang câu trả lời thực tế.

Có gì sai với băm dữ liệu người dùng, bạn yêu cầu? Vâng, nó đi xuống bề mặt tiếp xúc và bảo mật thông qua che khuất.

Hãy tưởng tượng trong một giây rằng bạn là kẻ tấn công. Bạn thấy một cookie mật mã được đặt cho bộ nhớ trong phiên của bạn. Nó rộng 32 ký tự. Trời ạ. Đó có thể là MD5 ...

Chúng ta cũng hãy tưởng tượng trong một giây rằng họ biết thuật toán mà bạn đã sử dụng. Ví dụ:

md5(salt+username+ip+salt)

Bây giờ, tất cả những kẻ tấn công cần làm là vũ phu "muối" (không thực sự là muối, nhưng nhiều hơn về sau), và giờ anh ta có thể tạo ra tất cả các mã thông báo giả mà anh ta muốn với bất kỳ tên người dùng nào cho địa chỉ IP của mình! Nhưng vũ phu - ép muối là khó, phải không? Chắc chắn rồi. Nhưng GPU hiện đại thì cực kỳ tốt. Và trừ khi bạn sử dụng đủ tính ngẫu nhiên trong nó (làm cho nó đủ lớn), nó sẽ nhanh chóng rơi xuống, và với nó là chìa khóa cho lâu đài của bạn.

Nói tóm lại, thứ duy nhất bảo vệ bạn là muối, thứ không thực sự bảo vệ bạn nhiều như bạn nghĩ.

Nhưng đợi đã!

Tất cả điều đó đã được chứng minh rằng kẻ tấn công biết thuật toán! Nếu nó bí mật và khó hiểu, thì bạn có an toàn không? SAI . Dòng suy nghĩ đó có một cái tên: Bảo mật thông qua che khuất , không bao giờ nên dựa vào.

Cách tốt hơn

Cách tốt hơn là không bao giờ để thông tin của người dùng rời khỏi máy chủ, ngoại trừ id.

Khi người dùng đăng nhập, tạo mã thông báo ngẫu nhiên lớn (128 đến 256 bit). Thêm vào bảng cơ sở dữ liệu để ánh xạ mã thông báo đến userid, sau đó gửi nó đến máy khách trong cookie.

Điều gì xảy ra nếu kẻ tấn công đoán mã thông báo ngẫu nhiên của người dùng khác?

Chà, hãy làm một số phép toán ở đây. Chúng tôi đang tạo mã thông báo ngẫu nhiên 128 bit. Điều đó có nghĩa là có:

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

Bây giờ, để cho thấy con số đó lớn đến mức nào, hãy tưởng tượng mọi máy chủ trên internet (giả sử 50.000.000 ngày nay) đang cố gắng ép buộc con số đó với tốc độ 1.000.000.000 mỗi giây. Trong thực tế, máy chủ của bạn sẽ tan chảy dưới tải như vậy, nhưng hãy chơi nó ra.

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

Vì vậy, 50 triệu triệu lượt đoán mỗi giây. Nhanh thật! Đúng?

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

Vậy 6,8 giây ...

Hãy cố gắng đưa số đó xuống mức thân thiện hơn.

215,626,585,489,599 years

Hoặc thậm chí tốt hơn:

47917 times the age of the universe

Phải, đó là 47917 lần tuổi của vũ trụ ...

Về cơ bản, nó sẽ không bị nứt.

Vì vậy, để tổng hợp:

Cách tiếp cận tốt hơn mà tôi khuyên bạn nên lưu trữ cookie với ba phần.

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

Sau đó, để xác nhận:

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

Lưu ý: Không sử dụng mã thông báo hoặc kết hợp người dùng và mã thông báo để tra cứu bản ghi trong cơ sở dữ liệu của bạn. Luôn đảm bảo tìm nạp bản ghi dựa trên người dùng và sử dụng chức năng so sánh an toàn theo thời gian để so sánh mã thông báo được tìm nạp sau đó. Tìm hiểu thêm về các cuộc tấn công thời gian .

Bây giờ, điều rất quan trọng SECRET_KEYlà bí mật mật mã (được tạo ra bởi một cái gì đó giống như /dev/urandomvà / hoặc có nguồn gốc từ một đầu vào entropy cao). Ngoài ra, GenerateRandomToken()cần phải được một nguồn ngẫu nhiên mạnh ( mt_rand()không phải là đủ gần mạnh. Sử dụng một thư viện, chẳng hạn như RandomLib hoặc random_compat , hoặc mcrypt_create_iv()với DEV_URANDOM) ...

Các hash_equals()là để ngăn chặn các cuộc tấn công thời gian . Nếu bạn sử dụng phiên bản PHP bên dưới PHP 5.6 thì chức năng hash_equals()không được hỗ trợ. Trong trường hợp này, bạn có thể thay thế hash_equals()bằng chức năng timeSafeCompare:

/**
 * 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
Nhưng không phải cách tiếp cận này có nghĩa là bất kỳ ai cũng có thể lấy tên người dùng và cookie này và đăng nhập với tư cách người dùng này từ bất kỳ thiết bị nào khác?
Đơn giản hơn

8
lol :-), lưu ý rằng 47917 năm là thời gian tối đa để đoán, mã thông báo ngẫu nhiên cũng có thể được đoán trong 1 giờ.
Storm_buster

33
Thật kỳ lạ vì mã của bạn mâu thuẫn với câu trả lời của bạn. Bạn nói "nếu bạn đưa dữ liệu người dùng vào cookie [...] bạn đang làm gì đó sai", nhưng đó chính xác là những gì mã của bạn đang làm! Không phải tốt hơn là xóa tên người dùng khỏi cookie, chỉ tính toán hàm băm qua mã thông báo (và có thể thêm địa chỉ IP để ngăn chặn hành vi trộm cắp cookie) và sau đó thực hiện fetchUsernameByToken thay vì fetchTokenByUserName trong missMe ()?
Leven

9
Kể từ PHP 5.6, hash_equals có thể được sử dụng để ngăn chặn các cuộc tấn công thời gian khi thực hiện so sánh chuỗi.
F21

5
@Levit nó ngăn người khác lấy mã thông báo hợp lệ và thay đổi userid được đính kèm.
ircmaxell

93

Thông báo bảo mật : Dựa vào cookie băm MD5 dữ liệu xác định là một ý tưởng tồi; tốt hơn là sử dụng mã thông báo ngẫu nhiên có nguồn gốc từ CSPRNG. Xem câu trả lời của ircmaxell cho câu hỏi này để có cách tiếp cận an toàn hơn.

Thông thường tôi làm một cái gì đó như thế này:

  1. Người dùng đăng nhập bằng 'giữ cho tôi đăng nhập'
  2. Tạo phiên
  3. Tạo một cookie có tên SOMETHING có chứa: md5 (salt + username + ip + salt) và một cookie gọi là SomethingElse chứa id
  4. Lưu trữ cookie trong cơ sở dữ liệu
  5. Người dùng làm công cụ và lá ----
  6. Người dùng trả lại, kiểm tra cookie gì đó, nếu nó tồn tại, hãy lấy hàm băm cũ từ cơ sở dữ liệu cho người dùng đó, kiểm tra nội dung của cookie SOMETHING khớp với hàm băm từ cơ sở dữ liệu, cũng khớp với hàm băm mới được tính toán (đối với ip) do đó: cookieHash == databaseHash == md5 (salt + username + ip + salt), nếu họ làm, goto 2, nếu họ không goto 1

Dĩ nhiên bạn có thể sử dụng các tên cookie khác nhau, v.v. bạn cũng có thể thay đổi nội dung của cookie một chút, chỉ cần đảm bảo rằng nó không dễ dàng được tạo. Ví dụ, bạn cũng có thể tạo user_salt khi người dùng được tạo và cũng đặt nó vào cookie.

Ngoài ra, bạn có thể sử dụng sha1 thay vì md5 (hoặc khá nhiều thuật toán)


30
Tại sao bao gồm IP trong hàm băm? Ngoài ra, hãy đảm bảo bao gồm thông tin dấu thời gian trong cookie và sử dụng thông tin này để thiết lập độ tuổi tối đa cho cookie để bạn không tạo mã thông báo nhận dạng tốt cho vĩnh viễn.
Scott Mitchell

4
@Abhishek Dilliwal: Đây là một chủ đề khá cũ nhưng tôi tình cờ thấy nó đang tìm kiếm câu trả lời giống như Mathew. Tôi không nghĩ việc sử dụng session_ID sẽ hoạt động cho câu trả lời của Pim vì bạn không thể kiểm tra hàm băm db, hàm băm cookie và session_ID hiện tại vì session_ID thay đổi mỗi session_start (); chỉ cần nghĩ rằng tôi sẽ chỉ ra điều này.
Partack

3
Tôi xin lỗi vì đã đần độn nhưng mục đích của cookie thứ hai là gì? Id trong trường hợp này là gì? Có phải nó chỉ là một loại giá trị "đúng / sai" đơn giản để cho biết liệu người dùng có muốn sử dụng tính năng giữ cho tôi đăng nhập không? Nếu vậy, tại sao không kiểm tra xem liệu cookie SOMETHING có tồn tại ở nơi đầu tiên không? Nếu người dùng không muốn đăng nhập của họ để tồn tại, cookie SOMETHING sẽ không ở đó ngay từ đầu phải không? Cuối cùng, bạn có tạo lại băm một cách linh hoạt và kiểm tra nó với cookie và DB như một biện pháp bảo mật bổ sung không?
itmequinn

4
Mã thông báo phải là RANDOM, không được kết nối với người dùng / IP của anh ấy / người dùng của anh ấy / bất cứ điều gì theo bất kỳ cách nào. Đó là lỗ hổng bảo mật lớn.
pamil

4
Tại sao bạn sử dụng hai muối? md5 (muối + tên người dùng + ip + muối)
Aaron Kreider

77

Giới thiệu

Tiêu đề của bạn Hãy giữ tôi đăng nhập vào - cách tiếp cận tốt nhất khiến tôi khó biết phải bắt đầu từ đâu vì nếu bạn đang tìm cách tiếp cận tốt nhất thì bạn sẽ phải cân nhắc những điều sau:

  • Nhận biết
  • Bảo vệ

Bánh quy

Cookie dễ bị tấn công, Giữa các lỗ hổng trộm cắp cookie thông thường của trình duyệt và các cuộc tấn công kịch bản chéo trang web, chúng tôi phải chấp nhận rằng cookie không an toàn. Để giúp cải thiện bảo mật, bạn phải lưu ý rằng php setcookiescó chức năng bổ sung như

bool setcookie (string $ name [, string $ value [, int $ hết hạn = 0 [, string $ path [, string $ miền [, bool $ đảm bảo = false [, bool $ HttpOnly = false]]]]]])

  • bảo mật (Sử dụng kết nối HTTPS)
  • httponly (Giảm hành vi trộm cắp danh tính thông qua tấn công XSS)

Định nghĩa

  • Mã thông báo (Chuỗi ngẫu nhiên không thể đoán trước có độ dài n, ví dụ: / dev / urandom)
  • Tham chiếu (Chuỗi ngẫu nhiên không thể đoán trước có độ dài n, ví dụ: / dev / urandom)
  • Chữ ký (Tạo giá trị băm có khóa bằng phương pháp HMAC)

Cách tiếp cận đơn giản

Một giải pháp đơn giản sẽ là:

  • Người dùng đã đăng nhập bằng Ghi nhớ
  • Đăng nhập Cookie được cấp bằng mã thông báo & Chữ ký
  • Khi trở về, Chữ ký được kiểm tra
  • Nếu Chữ ký ổn .. thì tên người dùng và mã thông báo được tra cứu trong cơ sở dữ liệu
  • nếu không hợp lệ .. quay lại trang đăng nhập
  • Nếu hợp lệ tự động đăng nhập

Nghiên cứu điển hình ở trên tóm tắt tất cả các ví dụ được đưa ra trên trang này nhưng chúng có nhược điểm là

  • Không có cách nào để biết nếu cookie đã bị đánh cắp
  • Kẻ tấn công có thể truy cập các hoạt động nhạy cảm như thay đổi mật khẩu hoặc dữ liệu như thông tin cá nhân và thông tin nướng, v.v.
  • Cookie bị xâm nhập vẫn có hiệu lực trong vòng đời của cookie

Giải pháp tốt hơn

Một giải pháp tốt hơn sẽ là

  • Người dùng đã đăng nhập và nhớ tôi được chọn
  • Tạo mã thông báo & chữ ký và lưu trữ trong cookie
  • Các mã thông báo là ngẫu nhiên và chỉ có giá trị cho tự động duy nhất
  • Mã thông báo được thay thế mỗi lần truy cập vào trang web
  • Khi người dùng không đăng nhập truy cập trang web, chữ ký, mã thông báo và tên người dùng được xác minh
  • Hãy nhớ tôi đăng nhập nên có quyền truy cập hạn chế và không cho phép sửa đổi mật khẩu, thông tin cá nhân, v.v.

Mã ví dụ

// 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());
}

Lớp được sử dụng

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

Thử nghiệm trong Firefox & Chrome

nhập mô tả hình ảnh ở đây

Lợi thế

  • An ninh tốt hơn
  • Truy cập hạn chế cho kẻ tấn công
  • Khi cookie bị đánh cắp, nó chỉ có giá trị cho một lần truy cập
  • Khi tiếp theo người dùng ban đầu truy cập trang web, bạn có thể tự động phát hiện và thông báo cho người dùng về hành vi trộm cắp

Bất lợi

  • Không hỗ trợ kết nối liên tục qua nhiều trình duyệt (Di động & Web)
  • Cookie vẫn có thể bị đánh cắp vì người dùng chỉ nhận được thông báo sau lần đăng nhập tiếp theo.

Khắc phục nhanh

  • Giới thiệu hệ thống phê duyệt cho mỗi hệ thống phải có kết nối liên tục
  • Sử dụng nhiều cookie để xác thực

Phương pháp tiếp cận nhiều cookie

Khi kẻ tấn công sắp đánh cắp cookie, chỉ tập trung vào một trang web hoặc tên miền cụ thể, vd. example.com

Nhưng thực sự bạn có thể xác thực người dùng từ 2 tên miền khác nhau ( example.com & fakeaddsite.com ) và làm cho nó trông giống như "Cookie quảng cáo"

  • Người dùng đã đăng nhập vào example.com bằng cách nhớ tôi
  • Lưu tên người dùng, mã thông báo, tham chiếu trong cookie
  • Lưu tên người dùng, mã thông báo, tham chiếu trong Cơ sở dữ liệu, vd. Memcache
  • Gửi id điều chỉnh thông qua get và iframe tới fakeaddsite.com
  • fakeaddsite.com sử dụng tham chiếu để tìm nạp người dùng & mã thông báo từ Cơ sở dữ liệu
  • fakeaddsite.com lưu trữ chữ ký
  • Khi người dùng trả về tìm nạp thông tin chữ ký với iframe từ fakeaddsite.com
  • Kết hợp dữ liệu và thực hiện xác nhận
  • ..... bạn biết phần còn lại

Một số người có thể tự hỏi làm thế nào bạn có thể sử dụng 2 cookie khác nhau? Vâng, nó có thể, tưởng tượng example.com = localhostfakeaddsite.com = 192.168.1.120. Nếu bạn kiểm tra các cookie, nó sẽ trông như thế này

nhập mô tả hình ảnh ở đây

Từ hình trên

  • Trang web hiện tại đã truy cập là localhost
  • Nó cũng chứa cookie được đặt từ 192.168.1.120

192.168.1.120

  • Chỉ chấp nhận được xác định HTTP_REFERER
  • Chỉ chấp nhận kết nối từ được chỉ định REMOTE_ADDR
  • Không có JavaScript, Không có nội dung nhưng không có gì ngoài việc ký thông tin và thêm hoặc truy xuất nó từ cookie

Lợi thế

  • 99% thời gian bạn đã lừa được kẻ tấn công
  • Bạn có thể dễ dàng khóa tài khoản trong lần tấn công đầu tiên
  • Tấn công có thể được ngăn chặn ngay cả trước khi đăng nhập tiếp theo như các phương thức khác

Bất lợi

  • Nhiều yêu cầu đến máy chủ chỉ cho một lần đăng nhập

Cải thiện

  • Sử dụng xong iframe ajax

5
Mặc dù @ircmaxell đã mô tả lý thuyết khá tốt, tôi thích cách tiếp cận này, vì nó hoạt động tuyệt vời mà không cần lưu trữ ID người dùng (sẽ là một tiết lộ không mong muốn) và cũng bao gồm nhiều dấu vân tay hơn chỉ là ID người dùng và hàm băm để xác định người dùng, chẳng hạn như trình duyệt. Điều này khiến kẻ tấn công sử dụng cookie bị đánh cắp càng khó hơn. Đó là cách tiếp cận tốt nhất và an toàn nhất tôi từng thấy cho đến nay. +1
Marcello Mönkemeyer

6

Tôi đã hỏi một góc của câu hỏi này ở đây và câu trả lời sẽ dẫn bạn đến tất cả các liên kết cookie hết thời gian dựa trên mã thông báo mà bạn cần.

Về cơ bản, bạn không lưu trữ userId trong cookie. Bạn lưu trữ mã thông báo một lần (chuỗi lớn) mà người dùng sử dụng để chọn phiên đăng nhập cũ của họ. Sau đó, để làm cho nó thực sự an toàn, bạn yêu cầu mật khẩu cho các hoạt động nặng (như tự thay đổi mật khẩu).


6

Chủ đề cũ, nhưng vẫn là một mối quan tâm hợp lệ. Tôi nhận thấy một số phản hồi tốt về bảo mật và tránh sử dụng "bảo mật thông qua che khuất", nhưng các phương pháp kỹ thuật thực tế được đưa ra là không đủ trong mắt tôi. Những điều tôi phải nói trước khi đóng góp phương pháp của mình:

  • KHÔNG BAO GIỜ lưu trữ mật khẩu trong văn bản rõ ràng ... EVER!
  • KHÔNG BAO GIỜ lưu trữ mật khẩu băm của người dùng ở nhiều hơn một vị trí trong cơ sở dữ liệu của bạn. Phụ trợ máy chủ của bạn luôn có khả năng lấy mật khẩu băm từ bảng người dùng. Sẽ không hiệu quả hơn khi lưu trữ dữ liệu dư thừa thay cho các giao dịch DB bổ sung, điều ngược lại là đúng.
  • Session ID của bạn phải là duy nhất, vì vậy không có hai người dùng có thể bao giờ chia sẻ một ID, do đó mục đích của một ID (có Giấy phép số ID của điều khiển của bạn bao giờ phù hợp với những người khác? Số) này tạo ra một sự kết hợp độc đáo hai mảnh, dựa trên 2 chuỗi độc đáo. Bảng Phiên của bạn nên sử dụng ID làm PK. Để cho phép nhiều thiết bị được tin cậy để đăng nhập tự động, hãy sử dụng bảng khác cho các thiết bị đáng tin cậy có chứa danh sách tất cả các thiết bị được xác thực (xem ví dụ của tôi bên dưới) và được ánh xạ bằng tên người dùng.
  • Nó không phục vụ mục đích để băm dữ liệu đã biết vào cookie, cookie có thể được sao chép. Những gì chúng tôi đang tìm kiếm là một thiết bị người dùng tuân thủ để cung cấp thông tin xác thực không thể có được mà không có kẻ tấn công xâm phạm máy của người dùng (một lần nữa, xem ví dụ của tôi). Tuy nhiên, điều này có nghĩa là người dùng hợp pháp cấm thông tin tĩnh của máy anh ta (ví dụ địa chỉ MAC, tên máy chủ thiết bị, người dùng nếu bị giới hạn bởi trình duyệt, v.v.) sẽ không thể nhất quán (hoặc giả mạo ở vị trí đầu tiên) sử dụng tính năng này. Nhưng nếu đây là một vấn đề đáng lo ngại, hãy xem xét thực tế rằng bạn đang cung cấp đăng nhập tự động cho người dùng tự nhận dạng duy nhất, vì vậy nếu họ từ chối được biết đến bằng cách giả mạo MAC của họ, giả mạo người dùng của họ, giả mạo / thay đổi tên máy chủ của họ, ẩn đằng sau proxy, v.v., thì họ không thể nhận dạng được và không bao giờ được xác thực cho dịch vụ tự động. Nếu bạn muốn điều này, bạn cần xem xét truy cập thẻ thông minh đi kèm với phần mềm phía máy khách để thiết lập danh tính cho thiết bị đang được sử dụng.

Như đã nói, có hai cách tuyệt vời để tự động đăng nhập vào hệ thống của bạn.

Đầu tiên, cách rẻ tiền, dễ dàng đặt tất cả vào người khác. Nếu bạn làm cho trang web của bạn hỗ trợ đăng nhập bằng, giả sử tài khoản google + của bạn, bạn có thể có một nút google + được sắp xếp hợp lý sẽ đăng nhập người dùng nếu họ đã đăng nhập vào google (tôi đã làm điều đó ở đây để trả lời câu hỏi này, vì tôi luôn luôn như vậy đăng nhập vào google). Nếu bạn muốn người dùng tự động đăng nhập nếu họ đã đăng nhập bằng trình xác thực được hỗ trợ và tin cậy và đã chọn hộp để làm như vậy, hãy để các tập lệnh phía máy khách của bạn thực hiện mã phía sau nút 'đăng nhập bằng' tương ứng trước khi tải , chỉ cần đảm bảo rằng máy chủ lưu trữ một ID duy nhất trong bảng đăng nhập tự động có tên người dùng, ID phiên và trình xác thực được sử dụng cho người dùng. Vì các phương thức đăng nhập này sử dụng AJAX, nên bạn vẫn đang chờ phản hồi, và phản hồi đó là phản hồi được xác thực hoặc từ chối. Nếu bạn nhận được phản hồi xác thực, hãy sử dụng nó như bình thường, sau đó tiếp tục tải người dùng đã đăng nhập như bình thường. Nếu không, đăng nhập thất bại, nhưng đừng nói với người dùng, chỉ cần tiếp tục như không đăng nhập, họ sẽ nhận thấy. Điều này là để ngăn kẻ tấn công đánh cắp cookie (hoặc giả mạo chúng trong nỗ lực leo thang đặc quyền) khi biết rằng người dùng tự động đăng nhập vào trang web.

Điều này là rẻ và cũng có thể bị một số người coi là bẩn vì nó cố gắng xác thực khả năng bạn đã đăng nhập với những nơi như Google và Facebook mà không hề nói cho bạn biết. Tuy nhiên, không nên sử dụng nó cho những người dùng chưa yêu cầu tự động đăng nhập trang web của bạn và phương pháp cụ thể này chỉ dành cho xác thực bên ngoài, như với Google+ hoặc FB.

Vì trình xác thực bên ngoài được sử dụng để báo cho máy chủ biết hậu trường người dùng có được xác thực hay không, kẻ tấn công không thể lấy bất cứ thứ gì ngoài ID duy nhất, bản thân nó vô dụng. Tôi sẽ giải thích:

  • Người dùng 'joe' truy cập trang web lần đầu tiên, ID phiên được đặt trong 'phiên' cookie.
  • Người dùng 'joe' Đăng nhập, leo thang đặc quyền, nhận ID phiên mới và gia hạn cookie 'phiên'.
  • Người dùng 'joe' chọn tự động đăng nhập bằng google +, nhận được một ID duy nhất được đặt trong cookie 'keepmesignin'.
  • Người dùng 'joe' có google giữ họ đăng nhập, cho phép trang web của bạn tự động đăng nhập người dùng bằng google trong phần phụ trợ của bạn.
  • Attacker cố gắng một cách có hệ thống các ID duy nhất cho 'keepmesignin' (đây là kiến ​​thức công khai được trao cho mọi người dùng) và không được đăng nhập vào bất kỳ nơi nào khác; thử ID duy nhất được cấp cho 'joe'.
  • Máy chủ nhận ID duy nhất cho 'joe', kéo khớp trong DB cho tài khoản google +.
  • Máy chủ gửi Attacker đến trang đăng nhập chạy yêu cầu AJAX tới google để đăng nhập.
  • Máy chủ Google nhận được yêu cầu, sử dụng API của nó để xem Attacker hiện chưa đăng nhập.
  • Google gửi phản hồi rằng hiện tại không có người dùng nào đăng nhập qua kết nối này.
  • Trang của kẻ tấn công nhận được phản hồi, tập lệnh tự động chuyển hướng đến trang đăng nhập với giá trị POST được mã hóa trong url.
  • Trang đăng nhập nhận giá trị POST, gửi cookie cho 'keepmesignin' đến một giá trị trống và có hiệu lực cho đến ngày 1-1-1970 để ngăn chặn nỗ lực tự động, khiến trình duyệt của Attacker chỉ cần xóa cookie.
  • Attacker được cung cấp trang đăng nhập lần đầu bình thường.

Dù thế nào đi chăng nữa, ngay cả khi kẻ tấn công sử dụng ID không tồn tại, nỗ lực sẽ thất bại trên tất cả các lần thử trừ khi nhận được phản hồi hợp lệ.

Phương pháp này có thể và nên được sử dụng cùng với trình xác thực nội bộ của bạn cho những người đăng nhập vào trang web của bạn bằng trình xác thực bên ngoài.

=========

Bây giờ, đối với hệ thống xác thực của riêng bạn có thể tự động đăng nhập người dùng, đây là cách tôi thực hiện:

DB có một vài bảng:

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

Lưu ý rằng tên người dùng có khả năng dài 255 ký tự. Tôi có chương trình máy chủ giới hạn tên người dùng trong hệ thống của mình ở mức 32 ký tự, nhưng trình xác thực bên ngoài có thể có tên người dùng với @ domain.tld của họ lớn hơn đó, vì vậy tôi chỉ hỗ trợ độ dài tối đa của địa chỉ email để tương thích tối đa.

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

Lưu ý rằng không có trường người dùng trong bảng này, vì tên người dùng, khi đăng nhập, nằm trong dữ liệu phiên và chương trình không cho phép dữ liệu null. Có thể tạo session_id và session_token bằng cách sử dụng băm md5 ngẫu nhiên, băm sha1 / 128/256, tem datetime với chuỗi ngẫu nhiên được thêm vào sau đó được băm hoặc bất cứ điều gì bạn muốn, nhưng entropy của đầu ra của bạn sẽ vẫn ở mức chấp nhận được giảm thiểu các cuộc tấn công vũ phu ngay cả khi lên khỏi mặt đất và tất cả các băm do lớp phiên của bạn tạo ra phải được kiểm tra các trận đấu trong bảng phiên trước khi thử thêm chúng.

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

Các địa chỉ MAC theo bản chất của chúng được coi là ĐỘC ĐÁO, do đó, có ý nghĩa rằng mỗi mục có một giá trị duy nhất. Tên máy chủ, mặt khác, có thể được sao chép trên các mạng riêng biệt hợp pháp. Có bao nhiêu người sử dụng "Home-PC" làm một trong những tên máy tính của họ? Tên người dùng được lấy từ dữ liệu phiên bởi phụ trợ máy chủ, vì vậy thao tác là không thể. Đối với mã thông báo, nên sử dụng cùng một phương pháp để tạo mã thông báo phiên cho các trang để tạo mã thông báo trong cookie để người dùng tự động đăng nhập. Cuối cùng, mã datetime được thêm vào khi người dùng cần xác nhận lại thông tin đăng nhập của họ. Hoặc cập nhật datetime này khi người dùng đăng nhập giữ nó trong vòng một vài ngày, hoặc buộc nó hết hạn bất kể lần đăng nhập cuối cùng chỉ giữ trong một tháng hoặc lâu hơn, bất kể thiết kế của bạn ra lệnh gì.

Điều này ngăn người nào đó giả mạo hệ thống MAC và tên máy chủ cho người dùng mà họ biết tự động đăng nhập. KHÔNG BAO GIỜyêu cầu người dùng giữ một cookie với mật khẩu của họ, xóa văn bản hoặc bằng cách khác. Có mã thông báo được tạo lại trên mỗi điều hướng trang, giống như mã thông báo phiên. Điều này giúp giảm đáng kể khả năng kẻ tấn công có thể lấy cookie mã thông báo hợp lệ và sử dụng nó để đăng nhập. Một số người sẽ cố gắng nói rằng kẻ tấn công có thể đánh cắp cookie từ nạn nhân và thực hiện một cuộc tấn công lại phiên để đăng nhập. Nếu kẻ tấn công có thể đánh cắp cookie (điều này là có thể), chắc chắn chúng đã xâm phạm toàn bộ thiết bị, nghĩa là chúng chỉ có thể sử dụng thiết bị để đăng nhập bằng mọi cách, đánh bại mục đích đánh cắp hoàn toàn cookie. Miễn là trang web của bạn chạy trên HTTPS (cần phải xử lý mật khẩu, số CC hoặc các hệ thống đăng nhập khác), bạn đã có đủ khả năng bảo vệ cho người dùng mà bạn có thể có trong trình duyệt.

Một lưu ý: dữ liệu phiên không nên hết hạn nếu bạn sử dụng đăng nhập tự động. Bạn có thể hết hạn khả năng tiếp tục phiên sai, nhưng xác thực vào hệ thống sẽ tiếp tục dữ liệu phiên nếu đó là dữ liệu liên tục được dự kiến ​​sẽ tiếp tục giữa các phiên. Nếu bạn muốn cả dữ liệu phiên liên tục và không liên tục, hãy sử dụng một bảng khác cho dữ liệu phiên liên tục với tên người dùng là PK và để máy chủ truy xuất dữ liệu như dữ liệu phiên thông thường, chỉ cần sử dụng một biến khác.

Khi đăng nhập đã đạt được theo cách này, máy chủ vẫn sẽ xác nhận phiên. Đây là nơi bạn có thể mã hóa các kỳ vọng cho các hệ thống bị đánh cắp hoặc bị xâm nhập; các mẫu và kết quả dự kiến ​​khác của thông tin đăng nhập vào dữ liệu phiên thường có thể dẫn đến kết luận rằng hệ thống đã bị tấn công hoặc cookie bị giả mạo để có quyền truy cập. Đây là nơi ISS Tech của bạn có thể đặt các quy tắc kích hoạt khóa tài khoản hoặc tự động xóa người dùng khỏi hệ thống đăng nhập tự động, giữ cho kẻ tấn công đủ lâu để người dùng xác định cách kẻ tấn công thành công và cách cắt chúng.

Là một lưu ý cuối cùng, hãy chắc chắn rằng mọi nỗ lực khôi phục, thay đổi mật khẩu hoặc lỗi đăng nhập đều vượt qua ngưỡng dẫn đến việc đăng nhập tự động bị vô hiệu hóa cho đến khi người dùng xác thực hợp lệ và thừa nhận điều này đã xảy ra.

Tôi xin lỗi nếu bất cứ ai đang mong đợi mã được đưa ra trong câu trả lời của tôi, điều đó sẽ không xảy ra ở đây. Tôi sẽ nói rằng tôi sử dụng PHP, jQuery và AJAX để chạy các trang web của mình và tôi KHÔNG BAO GIỜ sử dụng Windows như một máy chủ ... bao giờ hết.



4

Tạo một hàm băm, có thể chỉ với một bí mật mà bạn biết, sau đó lưu trữ nó trong DB của bạn để nó có thể được liên kết với người dùng. Nên làm việc khá tốt.


Đây sẽ là một định danh duy nhất được tạo khi người dùng được tạo hay nó sẽ thay đổi mỗi khi người dùng tạo cookie "Keep Me Logged In" mới?
Matthew

1
Câu trả lời của Tim Jansson mô tả một cách tiếp cận tốt để tạo ra hàm băm mặc dù tôi cảm thấy an toàn hơn nếu nó không bao gồm mật khẩu
Jani Hartikainen

2

Giải pháp của tôi là như thế này. Nó không chống đạn 100% nhưng tôi nghĩ nó sẽ giúp bạn tiết kiệm trong hầu hết các trường hợp.

Khi người dùng đăng nhập thành công, tạo một chuỗi với thông tin này:

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

Mã hóa $data, đặt loại thành httpOnly và đặt cookie.

Khi người dùng quay lại trang web của bạn, hãy thực hiện các bước này:

  1. Lấy dữ liệu cookie. Loại bỏ các ký tự nguy hiểm bên trong cookie. Phát nổ nó với: nhân vật.
  2. Kiểm tra tính hợp lệ. Nếu cookie cũ hơn X ngày thì chuyển hướng người dùng đến trang đăng nhập.
  3. Nếu cookie không cũ; Nhận thời gian thay đổi mật khẩu mới nhất từ ​​cơ sở dữ liệu. Nếu mật khẩu được thay đổi sau khi người dùng chuyển hướng đăng nhập cuối cùng của người dùng đến trang đăng nhập.
  4. Nếu pass không thay đổi gần đây; Nhận đại lý trình duyệt hiện tại của người dùng. Kiểm tra xem (currentUserAgentHash == cookieUserAgentHash). Các tác nhân IF giống nhau đi đến bước tiếp theo, khác chuyển hướng đến trang đăng nhập.
  5. Nếu tất cả các bước thông qua ủy quyền thành công tên người dùng.

Nếu người dùng đăng xuất, hãy xóa cookie này. Tạo cookie mới nếu người dùng đăng nhập lại.


2

Tôi không hiểu khái niệm lưu trữ nội dung được mã hóa trong cookie khi đó là phiên bản được mã hóa của nó mà bạn cần thực hiện hack. Nếu tôi thiếu một cái gì đó, xin vui lòng bình luận.

Tôi đang suy nghĩ về cách tiếp cận này để 'Nhớ tôi'. Nếu bạn có thể thấy bất kỳ vấn đề, xin vui lòng bình luận.

  1. Tạo một bảng để lưu trữ dữ liệu "Ghi nhớ tôi" - tách biệt với bảng người dùng để tôi có thể đăng nhập từ nhiều thiết bị.

  2. Khi đăng nhập thành công (với Nhớ tôi đã đánh dấu):

    a) Tạo một chuỗi ngẫu nhiên duy nhất được sử dụng làm UserID trên máy này: bigUserID

    b) Tạo một chuỗi ngẫu nhiên duy nhất: bigKey

    c) Lưu trữ cookie: bigUserID: bigKey

    d) Trong bảng "Ghi nhớ tôi", thêm bản ghi với: UserID, Địa chỉ IP, bigUserID, bigKey

  3. Nếu cố gắng truy cập một cái gì đó yêu cầu đăng nhập:

    a) Kiểm tra cookie và tìm kiếm bigUserID & bigKey với địa chỉ IP phù hợp

    b) Nếu bạn tìm thấy nó, Đăng nhập người nhưng đặt cờ trong bảng người dùng "đăng nhập mềm" để đối với mọi hoạt động nguy hiểm, bạn có thể nhắc đăng nhập đầy đủ.

  4. Khi đăng xuất, Đánh dấu tất cả các bản ghi "Ghi nhớ" cho người dùng đó là hết hạn.

Các lỗ hổng duy nhất mà tôi có thể thấy là;

  • bạn có thể giữ máy tính xách tay của ai đó và giả mạo địa chỉ IP của họ bằng cookie.
  • bạn có thể giả mạo một địa chỉ IP khác nhau mỗi lần và đoán toàn bộ - nhưng với hai chuỗi lớn khớp nhau, đó sẽ là ... thực hiện một phép tính tương tự như trên ... Tôi không biết ... tỷ lệ cược lớn?

Xin chào, và cảm ơn vì câu trả lời này, tôi thích nó. Một câu hỏi mặc dù: tại sao bạn phải tạo 2 chuỗi ngẫu nhiên - bigUserID & bigKey? Tại sao bạn không tạo chỉ 1 và sử dụng nó?
Jeremy Belolo

2
bigKey hết hạn sau một khoảng thời gian được xác định trước, nhưng bigUserID thì không. bigUserID là cho phép bạn có nhiều phiên trên các thiết bị khác nhau ở cùng một địa chỉ IP. Hy vọng điều đó có ý nghĩa - Tôi đã phải suy nghĩ một lúc :)
Enigma Plus

Một điều có hmac có thể giúp đỡ, là nếu bạn tìm thấy hmac bị giả mạo, bạn chắc chắn có thể biết rằng ai đó đã cố gắng ăn cắp cookie, sau đó bạn có thể đặt lại mọi trạng thái đăng nhập. Tôi có đúng không
Suraj Jain

2

Tôi đọc tất cả các câu trả lời và vẫn thấy khó khăn để trích xuất những gì tôi phải làm. Nếu một bức ảnh đáng giá 1k từ, tôi hy vọng điều này sẽ giúp người khác thực hiện lưu trữ liên tục an toàn dựa trên Cookie Đăng nhập liên tục được cải thiện của Barry Jaspan

nhập mô tả hình ảnh ở đây

Nếu bạn có câu hỏi, phản hồi hoặc đề xuất, tôi sẽ cố gắng cập nhật sơ đồ để phản ánh cho người mới đang cố gắng thực hiện đăng nhập liên tục an toàn.


0

Triển khai tính năng "Keep Me Logged In" có nghĩa là bạn cần xác định chính xác điều đó có nghĩa gì với người dùng. Trong trường hợp đơn giản nhất, tôi sẽ sử dụng điều đó có nghĩa là phiên có thời gian chờ lâu hơn nhiều: 2 ngày (giả sử) thay vì 2 giờ. Để làm điều đó, bạn sẽ cần bộ lưu trữ phiên của riêng bạn, có thể trong cơ sở dữ liệu, do đó bạn có thể đặt thời gian hết hạn tùy chỉnh cho dữ liệu phiên. Sau đó, bạn cần đảm bảo rằng bạn đã đặt cookie sẽ tồn tại trong một vài ngày (hoặc lâu hơn), thay vì hết hạn khi họ đóng trình duyệt.

Tôi có thể nghe bạn hỏi "tại sao 2 ngày? Tại sao không phải 2 tuần?". Điều này là do sử dụng một phiên trong PHP sẽ tự động đẩy thời hạn sử dụng trở lại. Điều này là do hết hạn phiên trong PHP thực sự là thời gian chờ không hoạt động.

Bây giờ, có thể nói rằng, có lẽ tôi đã triển khai một giá trị hết thời gian khó hơn mà tôi lưu trữ trong phiên và sau 2 tuần hoặc thêm mã để xem điều đó và buộc vô hiệu hóa phiên. Hoặc ít nhất là để đăng xuất chúng. Điều này có nghĩa là người dùng sẽ được yêu cầu đăng nhập định kỳ. Yahoo! thực hiện điều này.


1
Thiết lập một phiên còn có lẽ là xấu bởi vì nó lãng phí tài nguyên máy chủ và nó sẽ ảnh hưởng đến việc thực hiện tiêu cực
user3091530

0

Tôi nghĩ bạn có thể làm điều này:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Cửa hàng $cookiestring trong DB và đặt nó dưới dạng cookie. Đồng thời đặt tên người dùng của người đó làm cookie. Điểm chung của hàm băm là nó không thể được thiết kế ngược.

Khi người dùng bật lên, hãy lấy tên người dùng từ một cookie, hơn là $cookieStringtừ một người khác. Nếu $cookieStringkhớp với cái được lưu trong DB, thì người dùng được xác thực. Vì password_hash sử dụng một loại muối khác nhau mỗi lần, điều đó không liên quan đến văn bản rõ ràng là gì.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.