“Keep Me Logged In” - pendekatan terbaik

257

Aplikasi web saya menggunakan sesi untuk menyimpan informasi tentang pengguna setelah mereka masuk, dan untuk menjaga informasi tersebut saat mereka melakukan perjalanan dari halaman ke halaman dalam aplikasi. Dalam aplikasi khusus ini, saya menyimpan user_id, first_namedan last_nameorang tersebut.

Saya ingin menawarkan opsi "Tetap Saya Masuk" saat masuk yang akan menempatkan cookie pada mesin pengguna selama dua minggu, yang akan memulai kembali sesi mereka dengan detail yang sama ketika mereka kembali ke aplikasi.

Apa pendekatan terbaik untuk melakukan ini? Saya tidak ingin menyimpannya user_iddi cookie, karena sepertinya itu akan memudahkan bagi satu pengguna untuk mencoba dan menempa identitas pengguna lain.

Matius
sumber

Jawaban:

735

OK, saya jelaskan: jika Anda memasukkan data pengguna, atau apapun yang berasal dari data pengguna ke dalam cookie untuk tujuan ini, Anda melakukan sesuatu yang salah.

Sana. Saya mengatakannya. Sekarang kita bisa beralih ke jawaban yang sebenarnya.

Apa yang salah dengan hashing data pengguna, Anda bertanya? Yah, itu turun ke permukaan paparan dan keamanan melalui ketidakjelasan.

Bayangkan sesaat bahwa Anda seorang penyerang. Anda melihat cookie kriptografi yang diset untuk mengingat-saya di sesi Anda. Lebar 32 karakter. Wah. Itu mungkin MD5 ...

Mari kita bayangkan juga bahwa mereka mengetahui algoritma yang Anda gunakan. Sebagai contoh:

md5(salt+username+ip+salt)

Sekarang, yang perlu dilakukan penyerang hanyalah memaksa "garam" (yang sebenarnya bukan garam, tetapi lebih dari itu nanti), dan dia sekarang dapat menghasilkan semua token palsu yang dia inginkan dengan nama pengguna apa pun untuk alamat IP-nya! Tapi memaksakan diri dengan garam itu sulit, bukan? Benar. Tetapi GPU modern sangat bagus dalam hal itu. Dan kecuali Anda menggunakan keacakan yang cukup di dalamnya (membuatnya cukup besar), itu akan jatuh dengan cepat, dan dengan itu kunci ke istananya.

Singkatnya, satu-satunya hal yang melindungi Anda adalah garam, yang tidak benar-benar melindungi Anda seperti yang Anda pikirkan.

Tapi tunggu!

Semua itu diprediksikan bahwa penyerang tahu algoritma! Jika ini rahasia dan membingungkan, maka Anda aman, bukan? SALAH . Garis pemikiran itu memiliki nama: Keamanan Melalui Ketidakjelasan , yang TIDAK PERNAH bisa diandalkan.

Cara yang Lebih Baik

Cara yang lebih baik adalah tidak pernah membiarkan informasi pengguna meninggalkan server, kecuali untuk id.

Saat pengguna masuk, buat token acak besar (128 hingga 256 bit). Tambahkan itu ke tabel database yang memetakan token ke userid, dan kemudian kirimkan ke klien dalam cookie.

Bagaimana jika penyerang menebak token acak dari pengguna lain?

Baiklah, mari kita berhitung di sini. Kami membuat token acak 128 bit. Itu artinya ada:

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

Sekarang, untuk menunjukkan seberapa besar angka itu, mari bayangkan setiap server di internet (katakanlah 50.000.000 hari ini) mencoba untuk memaksa jumlah itu dengan kecepatan masing-masing 1.000.000.000 per detik. Pada kenyataannya server Anda akan meleleh di bawah beban seperti itu, tetapi mari kita lakukan ini.

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

Jadi 50 kuadriliun tebakan per detik. Itu cepat! Baik?

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

Jadi 6,8 sextillion detik ...

Mari kita coba bawa ke nomor yang lebih ramah.

215,626,585,489,599 years

Atau lebih baik lagi:

47917 times the age of the universe

Ya, itu 47917 kali usia alam semesta ...

Pada dasarnya, itu tidak akan retak.

Jadi ringkasnya:

Pendekatan yang lebih baik yang saya sarankan adalah menyimpan cookie dengan tiga bagian.

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

Kemudian, untuk memvalidasi:

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

Catatan: Jangan gunakan token atau kombinasi pengguna dan token untuk mencari catatan di database Anda. Selalu pastikan untuk mengambil catatan berdasarkan pengguna dan menggunakan fungsi perbandingan aman-waktu untuk membandingkan token yang diambil setelahnya. Lebih lanjut tentang serangan waktu .

Sekarang, sangat penting bahwa SECRET_KEYrahasia kriptografi (dihasilkan oleh sesuatu seperti /dev/urandomdan / atau berasal dari input entropi tinggi). Juga, GenerateRandomToken()harus menjadi sumber acak yang kuat ( mt_rand()tidak hampir cukup kuat. Gunakan perpustakaan, seperti RandomLib atau random_compat , atau mcrypt_create_iv()dengan DEV_URANDOM) ...

The hash_equals()adalah untuk mencegah serangan waktu . Jika Anda menggunakan versi PHP di bawah PHP 5.6 fungsi hash_equals()tidak didukung. Dalam hal ini Anda dapat mengganti hash_equals()dengan fungsi 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;
}
ircmaxell
sumber
7
Tetapi bukankah pendekatan ini berarti bahwa siapa pun dapat mengambil nama pengguna dan cookie ini dan masuk sebagai pengguna ini dari perangkat lain?
Simpler
8
lol :-), perhatikan bahwa 47917 tahun adalah waktu maksimum untuk menebak, token acak dapat ditebak dalam 1 jam juga.
storm_buster
33
Ini aneh karena kode Anda bertentangan dengan jawaban Anda. Anda mengatakan "jika Anda memasukkan data pengguna ke dalam cookie [...] Anda melakukan sesuatu yang salah", tetapi itulah yang dilakukan oleh kode Anda! Bukankah lebih baik untuk menghapus nama pengguna dari cookie, menghitung hash lebih dari token saja (dan mungkin menambahkan alamat ip untuk mencegah pencurian cookie) dan kemudian lakukan pengambilan Nama PenggunaByToken daripada fetchTokenByUserName di rememberMe ()?
Leven
9
Sejak PHP 5.6, hash_equals dapat digunakan untuk mencegah serangan timing ketika melakukan perbandingan string.
F21
5
@Levit mencegah seseorang mengambil token yang valid, dan mengubah userid yang terpasang padanya.
ircmaxell
93

Pemberitahuan Keamanan : Mendasarkan cookie dari hash data deterministik MD5 adalah ide yang buruk; lebih baik menggunakan token acak yang berasal dari CSPRNG. Lihat jawaban ircmaxell untuk pertanyaan ini untuk pendekatan yang lebih aman.

Biasanya saya melakukan sesuatu seperti ini:

  1. Pengguna masuk dengan 'tetap masuk'
  2. Buat sesi
  3. Buat cookie bernama SOMETHING yang mengandung: md5 (salt + username + ip + salt) dan cookie yang disebut somethingElse mengandung id
  4. Simpan cookie dalam basis data
  5. Pengguna melakukan hal-hal dan pergi ----
  6. Pengguna kembali, periksa sesuatu Cookie cookie, jika ada, dapatkan hash lama dari database untuk pengguna itu, periksa isi cookie SESUATU yang cocok dengan hash dari database, yang juga harus cocok dengan hash yang baru dihitung (untuk ip) demikian: cookieHash == databaseHash == md5 (garam + nama pengguna + ip + garam), jika ya, goto 2, jika mereka tidak kebagian 1

Tentu saja Anda dapat menggunakan nama cookie yang berbeda, dll. Anda juga dapat sedikit mengubah isi cookie, tapi pastikan itu tidak mudah dibuat. Misalnya, Anda juga dapat membuat user_salt saat pengguna dibuat dan juga memasukkannya ke dalam cookie.

Anda juga dapat menggunakan sha1 bukan md5 (atau cukup banyak algoritma apa pun)

Pim Jager
sumber
30
Mengapa menyertakan IP dalam hash? Selain itu, pastikan untuk memasukkan informasi cap waktu dalam cookie dan gunakan informasi ini untuk menetapkan usia maksimum untuk cookie sehingga Anda tidak membuat token identitas yang baik untuk selamanya.
Scott Mitchell
4
@Abhishek Dilliwal: Ini adalah utas yang cukup lama tetapi saya menemukan jawaban yang sama dengan Mathew. Saya tidak berpikir menggunakan session_ID akan bekerja untuk jawaban Pim karena Anda tidak dapat memeriksa hash db, hash cookie, dan session_ID saat ini karena session_ID mengubah setiap session_start (); hanya berpikir saya akan menunjukkan ini.
Partack
3
Saya minta maaf untuk menjadi tumpul tetapi apa tujuan dari cookie kedua sesuatuELSE? Apa id dalam hal ini? Apakah ini hanya semacam nilai "benar / salah" sederhana untuk menunjukkan apakah pengguna ingin menggunakan fitur tetap masuk log sama sekali? Jika demikian, mengapa tidak memeriksa untuk melihat apakah SESUATU cookie ada di tempat pertama? Jika pengguna tidak ingin login mereka berlanjut, cookie SESUATU tidak akan ada di sana, kan? Akhirnya, apakah Anda membuat hash lagi secara dinamis dan memeriksanya terhadap cookie dan DB sebagai ukuran keamanan tambahan?
itsmequinn
4
Token harus RANDOM, tidak terhubung dengan pengguna / IP-nya / agen pengguna / apa pun dengan cara apa pun. Ini cacat keamanan utama.
pamil
4
Mengapa Anda menggunakan dua garam? md5 (garam + nama pengguna + ip + garam)
Aaron Kreider
77

pengantar

Judul Anda “Tetap Tercatat Saya” - pendekatan terbaik mempersulit saya untuk tahu harus mulai dari mana karena jika Anda mencari pendekatan terbaik maka Anda harus mempertimbangkan hal-hal berikut:

  • Identifikasi
  • Keamanan

Kue

Cookie rentan, Antara kerentanan pencurian cookie pada browser umum dan serangan skrip lintas situs, kami harus menerima bahwa cookie tidak aman. Untuk membantu meningkatkan keamanan, Anda harus perhatikan bahwa php setcookiesada fungsi tambahan seperti

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

  • secure (Menggunakan koneksi HTTPS)
  • httponly (Mengurangi pencurian identitas melalui serangan XSS)

Definisi

  • Token (String acak yang tidak dapat diprediksi, misalnya n. / Dev / urandom)
  • Referensi (String acak n yang tidak dapat diprediksi, mis. / Dev / urandom)
  • Tanda Tangan (Menghasilkan nilai hash kunci menggunakan metode HMAC)

Pendekatan Sederhana

Solusi sederhana adalah:

  • Pengguna masuk dengan Remember Me
  • Cookie Login dikeluarkan dengan token & Tanda Tangan
  • Ketika kembali, tanda tangan diperiksa
  • Jika Tanda tangan tidak apa-apa .. maka nama pengguna & token dilihat dalam database
  • jika tidak valid .. kembali ke halaman login
  • Jika valid otomatis login

Studi kasus di atas merangkum semua contoh yang diberikan pada halaman ini tetapi kekurangannya adalah itu

  • Tidak ada cara untuk mengetahui apakah cookie itu dicuri
  • Penyerang dapat mengakses operasi sensitif seperti perubahan kata sandi atau data seperti informasi pribadi dan pembuatan dll.
  • Cookie yang dikompromikan masih akan valid untuk rentang masa pakai cookie

Solusi yang lebih baik

Solusi yang lebih baik

  • Pengguna masuk dan ingat saya dipilih
  • Hasilkan Token & tanda tangan dan simpan di cookie
  • Token bersifat acak dan hanya valid untuk autentikasi tunggal
  • Token diganti pada setiap kunjungan ke situs
  • Ketika pengguna yang tidak login mengunjungi situs, tanda tangan, token, dan nama pengguna diverifikasi
  • Ingat saya login harus memiliki akses terbatas dan tidak mengizinkan modifikasi kata sandi, informasi pribadi dll.

Kode Contoh

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

Kelas yang digunakan

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

Menguji di Firefox & Chrome

masukkan deskripsi gambar di sini

Keuntungan

  • Keamanan yang lebih baik
  • Akses terbatas untuk penyerang
  • Ketika cookie dicuri, itu hanya berlaku untuk akses tunggal
  • Ketika berikutnya pengguna asli mengakses situs Anda dapat secara otomatis mendeteksi dan memberi tahu pengguna pencurian

Kerugian

  • Tidak mendukung koneksi persisten melalui banyak browser (Mobile & Web)
  • Cookie masih dapat dicuri karena pengguna hanya mendapat notifikasi setelah login berikutnya.

Perbaikan Cepat

  • Pengenalan sistem persetujuan untuk setiap sistem yang harus memiliki koneksi persisten
  • Gunakan beberapa cookie untuk otentikasi

Berbagai Pendekatan Cookie

Ketika seorang penyerang akan mencuri cookie, fokus saja pada situs web atau domain tertentu misalnya. example.com

Tapi sungguh Anda dapat mengautentikasi pengguna dari 2 domain berbeda ( example.com & fakeaddsite.com ) dan membuatnya seperti "Iklan Cookie"

  • Pengguna Masuk ke example.com dengan mengingat saya
  • Simpan nama pengguna, token, referensi dalam cookie
  • Menyimpan nama pengguna, token, referensi dalam Database mis. Memcache
  • Kirim id referensi melalui get dan iframe ke fakeaddsite.com
  • fakeaddsite.com menggunakan referensi untuk mengambil pengguna & token dari Database
  • fakeaddsite.com menyimpan tanda tangan
  • Ketika pengguna kembali mengambil informasi tanda tangan dengan iframe dari fakeaddsite.com
  • Gabungkan data dan lakukan validasi
  • ..... kamu tahu sisanya

Beberapa orang mungkin bertanya-tanya bagaimana Anda bisa menggunakan 2 cookie yang berbeda? Yah itu mungkin, bayangkan example.com = localhostdan fakeaddsite.com = 192.168.1.120. Jika Anda memeriksa cookie itu akan terlihat seperti ini

masukkan deskripsi gambar di sini

Dari gambar di atas

  • Situs saat ini dikunjungi adalah localhost
  • Ini juga berisi cookie yang diatur dari 192.168.1.120

192.168.1.120

  • Hanya menerima yang ditentukan HTTP_REFERER
  • Hanya menerima koneksi dari yang ditentukan REMOTE_ADDR
  • Tidak Ada JavaScript, Tidak ada konten tetapi tidak mengandung apa pun selain menandatangani informasi dan menambahkan atau mengambilnya dari cookie

Keuntungan

  • 99% persen dari waktu Anda telah menipu si penyerang
  • Anda dapat dengan mudah mengunci akun dalam upaya penyerang pertama
  • Serangan dapat dicegah bahkan sebelum login berikutnya seperti metode lainnya

Kerugian

  • Permintaan berganda ke server hanya untuk satu login

Perbaikan

  • Selesai menggunakan iframe, gunakan ajax
Baba
sumber
5
Meskipun @ircmaxell menggambarkan teorinya dengan cukup baik, saya lebih suka pendekatan ini, karena ia bekerja dengan sangat baik tanpa perlu menyimpan ID pengguna (yang akan menjadi pengungkapan yang tidak diinginkan) dan juga mencakup lebih banyak sidik jari daripada hanya ID pengguna dan hash untuk mengidentifikasi pengguna, seperti browser. Ini membuat penyerang lebih sulit memanfaatkan cookie curian. Ini pendekatan terbaik dan paling aman yang saya lihat sejauh ini. +1
Marcello Mönkemeyer
6

Saya menanyakan satu sudut pertanyaan ini di sini , dan jawabannya akan mengarahkan Anda ke semua tautan cookie penghitungan waktu token yang Anda butuhkan.

Pada dasarnya, Anda tidak menyimpan ID pengguna dalam cookie. Anda menyimpan token satu kali (string besar) yang digunakan pengguna untuk mengambil sesi login lama mereka. Kemudian untuk membuatnya benar-benar aman, Anda meminta kata sandi untuk operasi berat (seperti mengubah kata sandi itu sendiri).

Dan Rosenstark
sumber
6

Utas lama, namun masih menjadi perhatian yang valid. Saya memperhatikan beberapa tanggapan yang baik tentang keamanan, dan menghindari penggunaan 'keamanan melalui ketidakjelasan', tetapi metode teknis yang sebenarnya diberikan tidak cukup di mata saya. Hal-hal yang harus saya katakan sebelum berkontribusi metode saya:

  • JANGAN PERNAH menyimpan kata sandi dalam teks yang jelas ... PERNAH!
  • TIDAK PERNAH menyimpan kata sandi hash pengguna di lebih dari satu lokasi di basis data Anda. Backend server Anda selalu mampu menarik kata sandi hash dari tabel pengguna. Tidaklah lebih efisien untuk menyimpan data yang berlebihan sebagai pengganti transaksi DB tambahan, kebalikannya adalah benar.
  • Sesi ID Anda harus unik, sehingga tidak ada dua pengguna bisa pernah berbagi ID, maka tujuan dari ID (bisa nomor ID SIM Anda pernah cocok orang lain? Tidak.) Ini menghasilkan kombinasi yang unik dua potong, berdasarkan 2 string yang unik. Tabel Sesi Anda harus menggunakan ID sebagai PK. Agar beberapa perangkat tepercaya untuk masuk otomatis, gunakan tabel lain untuk perangkat tepercaya yang berisi daftar semua perangkat yang divalidasi (lihat contoh saya di bawah), dan dipetakan menggunakan nama pengguna.
  • Tidak ada gunanya hash data diketahui menjadi cookie, cookie dapat disalin. Apa yang kami cari adalah perangkat pengguna yang patuh untuk memberikan informasi otentik yang tidak dapat diperoleh tanpa penyerang mengkompromikan mesin pengguna (sekali lagi, lihat contoh saya). Ini akan berarti, bagaimanapun, bahwa pengguna yang sah yang melarang informasi statis mesinnya (yaitu alamat MAC, nama host perangkat, agen pengguna jika dibatasi oleh browser, dll.) Dari tetap konsisten (atau menipu itu di tempat pertama) tidak akan dapat gunakan fitur ini. Tetapi jika ini merupakan masalah, pertimbangkan fakta bahwa Anda menawarkan masuk otomatis kepada pengguna yang mengidentifikasi diri mereka secara unik, jadi jika mereka menolak diketahui dengan memalsukan MAC mereka, memalsukan agen pengguna mereka, memalsukan / mengubah nama host mereka, bersembunyi di belakang proxy, dll., maka mereka tidak dapat diidentifikasi, dan tidak boleh diotentikasi untuk layanan otomatis. Jika Anda menginginkan ini, Anda perlu melihat akses kartu pintar yang dibundel dengan perangkat lunak sisi klien yang menetapkan identitas untuk perangkat yang digunakan.

Itu semua dikatakan, ada dua cara bagus untuk memiliki masuk otomatis pada sistem Anda.

Pertama, cara murah dan mudah yang menempatkan semuanya pada orang lain. Jika Anda membuat situs Anda mendukung login dengan, katakanlah, akun Google + Anda, Anda mungkin memiliki tombol google + ramping yang akan masuk pengguna jika mereka sudah masuk ke google (saya melakukan itu di sini untuk menjawab pertanyaan ini, karena saya selalu masuk ke google). Jika Anda ingin pengguna masuk secara otomatis jika mereka sudah masuk dengan autentikator yang tepercaya dan didukung, dan centang kotak untuk melakukannya, minta skrip sisi klien Anda melakukan kode di belakang tombol 'masuk dengan' yang sesuai sebelum memuat , pastikan server menyimpan ID unik di tabel masuk otomatis yang memiliki nama pengguna, ID sesi, dan autentikator yang digunakan untuk pengguna. Karena metode masuk ini menggunakan AJAX, Anda tetap menunggu respons, dan respons itu adalah respons tervalidasi atau penolakan. Jika Anda mendapatkan respons yang divalidasi, gunakan seperti biasa, lalu lanjutkan memuat pengguna yang masuk seperti biasa. Jika tidak, login gagal, tetapi jangan beri tahu pengguna, terus saja karena tidak masuk, mereka akan melihat. Ini untuk mencegah penyerang yang mencuri cookie (atau memalsunya dalam upaya untuk meningkatkan hak istimewa) dari mengetahui bahwa pengguna secara otomatis masuk ke situs.

Ini murah, dan mungkin juga dianggap kotor oleh beberapa orang karena mencoba memvalidasi diri Anda yang berpotensi sudah masuk sendiri dengan tempat-tempat seperti Google dan Facebook, bahkan tanpa memberi tahu Anda. Namun, itu tidak boleh digunakan pada pengguna yang belum meminta untuk masuk otomatis ke situs Anda, dan metode khusus ini hanya untuk otentikasi eksternal, seperti dengan Google+ atau FB.

Karena autentikator eksternal digunakan untuk memberi tahu server di balik layar apakah seorang pengguna divalidasi atau tidak, penyerang tidak dapat memperoleh apa pun selain ID unik, yang tidak berguna dengan sendirinya. Saya akan menguraikan:

  • Pengguna 'joe' mengunjungi situs untuk pertama kalinya, ID Sesi ditempatkan di 'sesi' cookie.
  • Pengguna 'joe' Masuk, meningkatkan hak istimewa, mendapatkan ID Sesi baru dan memperbarui 'sesi' cookie.
  • Pengguna 'joe' memilih untuk masuk otomatis menggunakan google +, mendapat ID unik yang ditempatkan di cookie 'keepmesignedin'.
  • Pengguna 'joe' telah membuat mereka tetap masuk di google, memungkinkan situs Anda untuk masuk secara otomatis dengan menggunakan google di backend Anda.
  • Penyerang secara sistematis mencoba ID unik untuk 'keepmesignedin' (ini adalah pengetahuan umum yang diberikan kepada setiap pengguna), dan tidak masuk ke tempat lain; mencoba ID unik yang diberikan kepada 'joe'.
  • Server menerima ID Unik untuk 'joe', menarik kecocokan dalam DB untuk akun google +.
  • Server mengirimkan Attacker ke halaman masuk yang menjalankan permintaan AJAX ke google untuk masuk.
  • Server Google menerima permintaan, menggunakan API untuk melihat Penyerang tidak masuk saat ini.
  • Google mengirimkan respons bahwa saat ini tidak ada pengguna yang masuk melalui koneksi ini.
  • Halaman penyerang menerima respons, skrip otomatis diarahkan ke halaman login dengan nilai POST yang disandikan di url.
  • Halaman login mendapatkan nilai POST, mengirimkan cookie untuk 'keepmesignedin' ke nilai kosong dan valid hingga tanggal 1-1-1970 untuk menghalangi upaya otomatis, yang menyebabkan browser Penyerang menghapus cookie dengan mudah.
  • Penyerang diberi halaman login pertama kali yang normal.

Tidak peduli apa, bahkan jika penyerang menggunakan ID yang tidak ada, upaya harus gagal pada semua upaya kecuali ketika respons yang divalidasi diterima.

Metode ini dapat dan harus digunakan bersama dengan autentikator internal Anda bagi mereka yang masuk ke situs Anda menggunakan autentikator eksternal.

=========

Sekarang, untuk sistem autentikator Anda sendiri yang dapat secara otomatis masuk pengguna, ini adalah bagaimana saya melakukannya:

DB memiliki beberapa tabel:

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

Perhatikan bahwa nama pengguna dapat mencapai 255 karakter. Saya memiliki program server saya membatasi nama pengguna di sistem saya menjadi 32 karakter, tetapi autentikator eksternal mungkin memiliki nama pengguna dengan @ domain.tld mereka lebih besar dari itu, jadi saya hanya mendukung panjang maksimum alamat email untuk kompatibilitas maksimum.

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

Perhatikan bahwa tidak ada bidang pengguna dalam tabel ini, karena nama pengguna, ketika login, ada di data sesi, dan program tidak mengizinkan data nol. The session_id dan session_token dapat dihasilkan menggunakan hash md5 acak, hash sha1 / 128/256, stempel datetime dengan string acak ditambahkan ke mereka kemudian hash, atau apa pun yang Anda inginkan, tetapi entropi output Anda harus tetap setinggi yang dapat ditoleransi untuk mengurangi serangan brute-force bahkan setelah turun dari tanah, dan semua hash yang dihasilkan oleh kelas sesi Anda harus diperiksa untuk pertandingan di tabel sesi sebelum mencoba menambahkannya.

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

Alamat MAC menurut sifatnya seharusnya UNIK, oleh karena itu masuk akal bahwa setiap entri memiliki nilai unik. Hostnames, di sisi lain, dapat diduplikasi di jaringan terpisah secara sah. Berapa banyak orang yang menggunakan "Home-PC" sebagai salah satu nama komputer mereka? Nama pengguna diambil dari data sesi oleh server backend, jadi memanipulasi itu tidak mungkin. Adapun token, metode yang sama untuk menghasilkan token sesi untuk halaman harus digunakan untuk menghasilkan token di cookie untuk pengguna masuk otomatis pengguna. Terakhir, kode datetime ditambahkan ketika pengguna perlu memvalidasi ulang kredensial mereka. Entah perbarui datetime ini pada login pengguna yang menyimpannya dalam beberapa hari, atau paksakan untuk kedaluwarsa terlepas dari login terakhir yang menyimpannya hanya selama sebulan atau lebih, mana pun yang ditentukan oleh desain Anda.

Ini mencegah seseorang dari spoofing MAC secara sistematis dan nama host untuk pengguna yang mereka kenal masuk otomatis. TIDAK PERNAHminta pengguna menyimpan cookie dengan kata sandi, menghapus teks atau sebaliknya. Buat token diregenerasi pada setiap navigasi halaman, sama seperti Anda melakukan token sesi. Ini secara besar-besaran mengurangi kemungkinan penyerang bisa mendapatkan cookie token yang valid dan menggunakannya untuk login. Beberapa orang akan mencoba mengatakan bahwa seorang penyerang dapat mencuri cookie dari korban dan melakukan serangan replay sesi untuk login. Jika penyerang bisa mencuri cookie (yang mungkin), mereka pasti akan membahayakan seluruh perangkat, yang berarti mereka bisa saja menggunakan perangkat untuk login, yang mengalahkan tujuan mencuri cookie sepenuhnya. Selama situs Anda menggunakan HTTPS (yang seharusnya ketika berhadapan dengan kata sandi, nomor CC, atau sistem masuk lainnya), Anda telah memberikan semua perlindungan kepada pengguna yang Anda bisa dalam browser.

Satu hal yang perlu diingat: data sesi tidak boleh kedaluwarsa jika Anda menggunakan masuk otomatis. Anda dapat kedaluwarsa kemampuan untuk melanjutkan sesi dengan salah, tetapi memvalidasi ke dalam sistem harus melanjutkan data sesi jika itu adalah data persisten yang diharapkan untuk berlanjut di antara sesi. Jika Anda menginginkan data sesi persisten dan non-persisten, gunakan tabel lain untuk data sesi persisten dengan nama pengguna sebagai PK, dan minta server mengambilnya seperti data sesi normal, cukup gunakan variabel lain.

Setelah masuk dengan cara ini, server masih harus memvalidasi sesi. Di sinilah Anda bisa memberi kode harapan untuk sistem yang dicuri atau dikompromikan; pola dan hasil lain yang diharapkan dari login ke data sesi sering dapat mengarah pada kesimpulan bahwa suatu sistem dibajak atau cookie dipalsukan untuk mendapatkan akses. Di sinilah Tek ISS Anda dapat menetapkan aturan yang akan memicu penguncian akun atau penghapusan otomatis pengguna dari sistem masuk otomatis, membuat penyerang keluar cukup lama bagi pengguna untuk menentukan bagaimana penyerang berhasil dan cara memotongnya.

Sebagai catatan penutup, pastikan bahwa setiap upaya pemulihan, perubahan kata sandi, atau kegagalan login melewati ambang batas mengakibatkan penandatanganan otomatis dinonaktifkan hingga pengguna memvalidasi dengan benar dan mengakui hal ini telah terjadi.

Saya minta maaf jika ada yang mengharapkan kode untuk diberikan dalam jawaban saya, itu tidak akan terjadi di sini. Saya akan mengatakan bahwa saya menggunakan PHP, jQuery, dan AJAX untuk menjalankan situs saya, dan saya TIDAK PERNAH menggunakan Windows sebagai server ... selamanya.

pengguna253780
sumber
5

Saya akan merekomendasikan pendekatan yang disebutkan oleh Stefan (yaitu ikuti panduan dalam Praktek Terbaik Cookie Persistent Improved Login ) dan juga merekomendasikan agar Anda memastikan cookie Anda adalah cookie HttpOnly sehingga mereka tidak dapat diakses oleh, yang berpotensi berbahaya, JavaScript.

Walter Rumsby
sumber
4

Hasilkan hash, mungkin hanya dengan rahasia yang Anda tahu, lalu simpan di DB Anda sehingga dapat dikaitkan dengan pengguna. Harus bekerja dengan baik.

Jani Hartikainen
sumber
Apakah ini pengidentifikasi unik yang dibuat saat pengguna dibuat, atau apakah itu berubah setiap kali pengguna membuat cookie "Keep Me Logged In" yang baru?
Matius
1
Jawaban Tim Jansson menggambarkan pendekatan yang baik untuk menghasilkan hash meskipun saya akan merasa lebih aman jika tidak memasukkan kata sandi
Jani Hartikainen
2

Solusi saya seperti ini. Ini bukan 100% anti peluru, tapi saya pikir itu akan menyelamatkan Anda untuk sebagian besar kasus.

Ketika pengguna masuk berhasil membuat string dengan informasi ini:

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

Enkripsi $data, atur tipe ke HttpOnly dan atur cookie.

Ketika pengguna kembali ke situs Anda, Buat langkah-langkah ini:

  1. Dapatkan data cookie. Hapus karakter berbahaya di dalam cookie. Ledak dengan: karakter.
  2. Periksa validitas. Jika cookie lebih lama dari X hari maka arahkan pengguna ke halaman login.
  3. Jika cookie tidak lama; Dapatkan waktu penggantian kata sandi terbaru dari basis data. Jika kata sandi diubah setelah pengguna login terakhir, pengguna redirect ke halaman login.
  4. Jika pass tidak diubah baru-baru ini; Dapatkan agen peramban pengguna saat ini. Periksa apakah (currentUserAgentHash == cookieUserAgentHash). JIKA agen sama lanjutkan ke langkah berikutnya, kalau tidak redirect ke halaman login.
  5. Jika semua langkah berhasil melewati otorisasi nama pengguna.

Jika pengguna keluar, hapus cookie ini. Buat cookie baru jika pengguna masuk kembali.

trante
sumber
2

Saya tidak mengerti konsep menyimpan hal-hal terenkripsi dalam cookie ketika itu adalah versi terenkripsi yang Anda butuhkan untuk melakukan peretasan. Jika saya kehilangan sesuatu, silakan komentar.

Saya sedang berpikir untuk mengambil pendekatan ini ke 'Remember Me'. Jika Anda dapat melihat masalah apa pun, silakan komentar.

  1. Buat tabel untuk menyimpan data "Ingat Saya" di - pisahkan dengan tabel pengguna sehingga saya bisa masuk dari beberapa perangkat.

  2. Saat login berhasil (dengan Ingat Saya):

    a) Hasilkan string acak unik untuk digunakan sebagai UserID pada mesin ini: bigUserID

    b) Menghasilkan string acak yang unik: bigKey

    c) Simpan cookie: bigUserID: bigKey

    d) Dalam tabel "Remember Me", tambahkan catatan dengan: UserID, Alamat IP, bigUserID, bigKey

  3. Jika mencoba mengakses sesuatu yang memerlukan login:

    a) Periksa cookie dan cari bigUserID & bigKey dengan alamat IP yang cocok

    b) Jika Anda menemukannya, Log orang tersebut tetapi tetapkan bendera di tabel pengguna "login lunak" sehingga untuk operasi berbahaya, Anda dapat meminta login penuh.

  4. Saat logout, Tandai semua catatan "Ingat Saya" untuk pengguna itu sebagai kedaluwarsa.

Satu-satunya kerentanan yang bisa saya lihat adalah;

  • Anda bisa mendapatkan laptop seseorang dan memalsukan alamat IP mereka dengan cookie.
  • Anda bisa menipu alamat IP yang berbeda setiap kali dan menebak semuanya - tetapi dengan dua string besar yang cocok, itu akan ... melakukan perhitungan yang sama dengan di atas ... Saya tidak tahu ... peluang besar?
Enigma Plus
sumber
Halo, dan terima kasih atas jawaban ini, saya menyukainya. Namun satu pertanyaan: mengapa Anda harus membuat 2 string acak - bigUserID & bigKey? Mengapa Anda tidak menghasilkan 1 saja dan menggunakannya?
Jeremy Belolo
2
bigKey berakhir setelah jumlah waktu yang telah ditentukan, tetapi bigUserID tidak. bigUserID adalah untuk memungkinkan Anda melakukan beberapa sesi pada perangkat yang berbeda pada alamat IP yang sama. Harapan itu masuk akal - saya harus berpikir sejenak :)
Enigma Plus
Satu hal memiliki hmac dapat membantu, adalah jika Anda menemukan hmac dirusak, Anda pasti dapat mengetahui bahwa seseorang mencoba mencuri cookie, maka Anda dapat mengatur ulang setiap keadaan login. Apakah saya benar?
Suraj Jain
2

Saya membaca semua jawaban dan masih kesulitan untuk mengekstrak apa yang seharusnya saya lakukan. Jika gambar bernilai 1rb kata, saya harap ini membantu orang lain menerapkan penyimpanan persisten yang aman berdasarkan Praktek Terbaik Cookie Persisten yang Ditingkatkan Barry Jaspan

masukkan deskripsi gambar di sini

Jika Anda memiliki pertanyaan, umpan balik, atau saran, saya akan mencoba memperbarui diagram untuk mencerminkan bagi pemula yang mencoba menerapkan login persisten yang aman.

Josh Woodcock
sumber
0

Menerapkan fitur "Keep Me Logged In" berarti Anda harus menentukan dengan tepat apa artinya bagi pengguna. Dalam kasus paling sederhana, saya akan menggunakan itu berarti sesi memiliki batas waktu lebih lama: 2 hari (katakanlah) daripada 2 jam. Untuk melakukan itu, Anda akan memerlukan penyimpanan sesi Anda sendiri, mungkin dalam database, sehingga Anda dapat mengatur waktu kedaluwarsa khusus untuk data sesi. Maka Anda perlu memastikan bahwa Anda menetapkan cookie yang akan bertahan selama beberapa hari (atau lebih lama), daripada kedaluwarsa ketika mereka menutup browser.

Saya dapat mendengar Anda bertanya "mengapa 2 hari? Mengapa tidak 2 minggu?". Ini karena menggunakan sesi dalam PHP akan secara otomatis mendorong kedaluwarsa kembali. Ini karena kedaluwarsa sesi dalam PHP sebenarnya adalah waktu tunggu idle.

Sekarang, setelah mengatakan itu, saya mungkin akan menerapkan nilai batas waktu lebih keras yang saya simpan di sesi itu sendiri, dan keluar pada 2 minggu atau lebih, dan menambahkan kode untuk melihat itu dan untuk secara paksa membatalkan sesi. Atau setidaknya untuk log out. Ini berarti bahwa pengguna akan diminta untuk masuk secara berkala. Yahoo! Melakukan hal ini.

ahli statika
sumber
1
Menetapkan sesi yang lebih lama mungkin buruk karena pemborosan sumber daya server dan itu akan mempengaruhi kinerja yang merugikan
user3091530
0

Saya pikir Anda bisa melakukan ini:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Toko $cookiestring di DB dan dan tetapkan sebagai cookie. Juga tetapkan nama pengguna orang tersebut sebagai cookie. Inti dari hash adalah tidak dapat direkayasa ulang.

Saat pengguna muncul, dapatkan nama pengguna dari satu cookie, daripada $cookieStringdari yang lain. Jika $cookieStringcocok dengan yang disimpan dalam DB, maka pengguna diautentikasi. Karena password_hash menggunakan garam yang berbeda setiap kali, itu tidak relevan untuk apa teks yang jelas itu.

Hayden O'Sullivan
sumber