Tutorial Pasukan Lucee MY

Log masuk, sesi dan kata laluan yang disimpan betul

Tambah log masuk dalam CFML dengan PBKDF2, garam unik setiap pengguna, sesi Lucee dan pengawal halaman supaya harga tidak boleh diubah oleh orang luar.

Log masuk, sesi dan kata laluan yang disimpan betul
0
Kandungan

Log masuk, sesi dan kata laluan yang disimpan betul

Setakat ini, sesiapa sahaja yang membuka produk-ubah.cfm boleh menukar harga. Itu tidak boleh diterima walaupun untuk kedai kecil, kerana alamat itu mudah diteka.

Bab ini menambah log masuk: satu jadual pengguna, satu halaman masuk, dan satu pengawal yang melindungi halaman yang mengubah data. Halaman yang dilindungi ialah yang anda bina dalam bab ubah dan arkib serta bab borang.

Apakah sesi

Apabila anda log masuk, pelayan perlu mengingati anda antara satu permintaan dengan permintaan seterusnya. HTTP tidak menyimpan ingatan itu sendiri, jadi Lucee menyediakan sesi.

Sesi ialah sekeping data yang disimpan di pelayan, dikaitkan dengan satu pelayar melalui cookie. Apabila anda membuka halaman lain, pelayar menghantar cookie itu, dan Lucee memulihkan data sesi anda.

Dalam CFML, data itu disimpan dalam session. Kita akan menyimpan satu struct kecil di sana: id, nama dan peranan pengguna. Struktur aplikasi ini masih sama seperti yang anda bina dalam bab pangkalan data, cuma kita menambah satu jadual baharu.

Aktifkan sesi

Sesi tidak aktif secara lalai. Buka Application.cfc dan tambah dua baris:

component {
    this.name = "inventori-mini";
    this.sessionManagement = true;
    this.sessionTimeout = createTimeSpan(0, 2, 0, 0);

    this.datasources["inventori"] = {
        type: "mysql",
        host: "127.0.0.1",
        port: 3306,
        database: "inventori",
        username: "root",
        password: ""
    };
}

createTimeSpan(0, 2, 0, 0) bermaksud hari, jam, minit dan saat. Nilai ini ialah dua jam. Selepas itu, pengguna perlu log masuk semula.

Jadual pengguna

Jalankan arahan ini dalam phpMyAdmin:

CREATE TABLE pengguna (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nama_pengguna VARCHAR(60) NOT NULL UNIQUE,
    kata_laluan VARCHAR(120) NOT NULL,
    garam VARCHAR(64) NOT NULL,
    peranan VARCHAR(20) NOT NULL DEFAULT 'staf',
    dicipta_pada DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Perhatikan bahawa tiada lajur kata_laluan_asli. Kita tidak pernah menyimpan kata laluan sebenar. Kita menyimpan satu hash, iaitu hasil matematik sehala yang tidak boleh diterbalikkan.

Memilih cara menyimpan kata laluan

Saya menguji Lucee 7.1.1.17 untuk fungsi hashing. Daripada 783 fungsi terbina, hanya ada hash dan hash40. Tiada generateBCryptHash, tiada Argon2.

Yang ada ialah generatePBKDFKey, dan ia sesuai untuk tujuan ini. PBKDF2 ialah piawaian yang direka khusus untuk kata laluan, kerana ia sengaja lambat.

ParameterNilai kitaMaksud
AlgoritmaPBKDF2WithHmacSHA256Kaedah terbitan kunci
Garam32 aksara rawakMenghalang jadual hash siap sedia
Lelaran120,000Melambatkan cubaan meneka
Saiz kunci256 bitPanjang hasil

Pada rig ujian, satu hash mengambil masa sekitar 123 milisaat. Itu hampir tidak dirasai ketika log masuk, tetapi ia melambatkan sesiapa yang cuba meneka beribu-ribu kata laluan.

Cipta pengguna pertama

Cipta fail sementara bernama cipta-admin.cfm, jalankan sekali dalam pelayar, kemudian padam fail itu.

<cfset garam = replace(createUUID(), "-", "", "all")>
<cfset hash = generatePBKDFKey("PBKDF2WithHmacSHA256", "rahsia123", garam, 120000, 256)>

<cfquery datasource="inventori">
    DELETE FROM pengguna WHERE nama_pengguna = 'admin'
</cfquery>

<cfquery datasource="inventori">
    INSERT INTO pengguna (nama_pengguna, kata_laluan, garam, peranan)
    VALUES (
        <cfqueryparam value="admin" cfsqltype="cf_sql_varchar">,
        <cfqueryparam value="#hash#" cfsqltype="cf_sql_varchar">,
        <cfqueryparam value="#garam#" cfsqltype="cf_sql_varchar">,
        <cfqueryparam value="admin" cfsqltype="cf_sql_varchar">
    )
</cfquery>

<cfoutput>Pengguna admin dicipta. Padam fail ini sekarang.</cfoutput>

Baris DELETE itu membolehkan anda menjalankan semula skrip ini tanpa ralat pendua. Tukar rahsia123 kepada kata laluan anda sendiri sebelum menjalankannya. Selepas pengguna dicipta, padam fail itu daripada folder projek.

Halaman log masuk

Fail log-masuk.cfm melakukan tiga perkara: membaca pengguna, mengesahkan kata laluan, dan menyimpan sesi. Ia menggunakan susun atur yang sama seperti halaman lain dalam bab header dan jadual, supaya borang itu kelihatan seragam.

<cfinclude template="includes/atas.cfm">

<cfset ralat = "">
<cfset namaPengguna = "">

<cfif structKeyExists(form, "hantar")>
    <cfset namaPengguna = trim(form.nama_pengguna ?: "")>
    <cfset kataLaluan = form.kata_laluan ?: "">

    <cfquery name="calon" datasource="inventori">
        SELECT id, nama_pengguna, kata_laluan, garam, peranan
        FROM pengguna
        WHERE nama_pengguna = <cfqueryparam value="#namaPengguna#" cfsqltype="cf_sql_varchar">
    </cfquery>

    <cfset sah = false>
    <cfif calon.recordCount EQ 1>
        <cfset cubaan = generatePBKDFKey("PBKDF2WithHmacSHA256", kataLaluan, calon.garam, 120000, 256)>
        <cfif cubaan EQ calon.kata_laluan>
            <cfset sah = true>
        </cfif>
    </cfif>

    <cfif sah>
        <cfset session.pengguna = {
            id: calon.id,
            nama: calon.nama_pengguna,
            peranan: calon.peranan
        }>
        <cfset sessionRotate()>
        <cflocation url="index.cfm" addtoken="false">
    <cfelse>
        <cfset ralat = "Nama pengguna atau kata laluan tidak sepadan.">
    </cfif>
</cfif>

<h2>Log masuk</h2>

<cfif len(ralat)>
    <cfoutput><div class="ralat-kotak">#ralat#</div></cfoutput>
</cfif>

<cfoutput>
<form method="post" action="log-masuk.cfm" class="borang">
    <label>Nama pengguna
        <input type="text" name="nama_pengguna" value="#encodeForHTMLAttribute(namaPengguna)#" autocomplete="username">
    </label>

    <label>Kata laluan
        <input type="password" name="kata_laluan" autocomplete="current-password">
    </label>

    <button type="submit" name="hantar" value="1">Masuk</button>
</form>
</cfoutput>

<cfinclude template="includes/bawah.cfm">

Ada tiga keputusan penting dalam kod ini.

Pertama, garam diambil daripada rekod pengguna, bukan dijana semula. Tanpa garam yang sama, hash tidak akan sepadan.

Kedua, mesej ralat tidak memberitahu sama ada nama pengguna wujud. Kalau ia berkata "pengguna tidak dijumpai", penyerang boleh mengumpul senarai nama yang sah satu demi satu. Satu mesej untuk semua kes lebih selamat.

Ketiga, sessionRotate() dipanggil selepas log masuk berjaya. Ia menukar pengecam sesi, supaya pengecam lama yang mungkin diketahui penyerang tidak lagi sah. Saya uji fungsi ini pada Lucee 7.1.1.17: ia berfungsi, dan data sesi kekal selepas ia dipanggil.

Pengawal halaman

Sekarang kita perlukan satu fail kecil yang menghalang akses. Cipta includes/semakan-login.cfm:

<cfif NOT structKeyExists(session, "pengguna")>
    <cflocation url="log-masuk.cfm" addtoken="false">
</cfif>

Kemudian letakkan satu baris ini di baris pertama setiap halaman yang mengubah data:

<cfinclude template="includes/semakan-login.cfm">

Fail yang perlu dilindungi ialah produk-tambah.cfm, produk-ubah.cfm dan produk-arkib.cfm. Halaman senarai boleh dibiarkan terbuka, atau dilindungi juga jika anda mahu.

Peraturan mudah: kalau halaman itu menulis ke pangkalan data, ia mesti ada pengawal ini. Jangan sesekali bergantung pada menyembunyikan butang di antara muka, kerana alamat boleh ditaip terus.

Log keluar

<cfset structClear(session)>
<cflocation url="log-masuk.cfm?keluar=1" addtoken="false">

structClear membuang semua data sesi, termasuk pengguna. Selepas itu, pengawal akan menghantar pengguna kembali ke halaman log masuk.

Halaman log masuk dengan mesej ralat yang sama untuk semua kes

Hasil sebenar pada rig ujian kami: mesej tidak mendedahkan sama ada nama pengguna wujud.

Ujian yang saya jalankan

Saya menguji aliran ini dengan curl dan cookie jar, supaya setiap langkah disahkan dan bukan diandaikan:

  • Membuka produk-tambah.cfm tanpa log masuk memberi 302 ke log-masuk.cfm.
  • Kata laluan salah memberi mesej ralat yang sama seperti nama pengguna yang tidak wujud.
  • Kata laluan betul memberi 302 ke index.cfm, dan halaman tambah kemudian memberi 200.
  • Selepas log keluar, halaman tambah kembali 302.

Pepijat yang berulang tiga kali

Mesej ralat muncul sebagai #ralat# dan bukan mesej sebenar. Puncanya sama seperti dua bab sebelum ini: #...# di luar <cfoutput> dicetak sebagai teks.

Kali ini saya menulis satu skrip kecil yang mengimbas setiap blok kod CFML dalam artikel dan melaporkan #...# yang berada di luar <cfoutput>. Skrip itu kini berjalan sebelum setiap artikel diterbitkan, kerana ujian lari tidak menangkapnya: halaman tetap sah, cuma mesejnya salah.

Apa yang belum ada

Borang log masuk ini belum mempunyai had cubaan. Sesiapa yang tahu nama pengguna boleh mencuba beribu-ribu kata laluan. PBKDF2 melambatkan setiap cubaan, tetapi ia tidak menghentikan cubaan berterusan.

Kita akan menambah kunci selepas beberapa cubaan gagal dalam bab keselamatan, bersama token CSRF dan beberapa perkara lain.

Soalan Lazim

Boleh saya menyimpan kata laluan dengan hash() sahaja? Boleh secara teknikal, tetapi jangan. hash() terlalu cepat, jadi berjuta-juta tekaan boleh dibuat dalam satu minit. Gunakan generatePBKDFKey.

Kenapa garam perlu berbeza setiap pengguna? Supaya dua pengguna dengan kata laluan yang sama tidak menghasilkan hash yang sama. Tanpanya, satu jadual hash yang dicuri boleh mendedahkan banyak akaun sekaligus.

Adakah sesi selamat melalui HTTP biasa? Tidak sepenuhnya. Cookie boleh dicuri dalam rangkaian terbuka. Di pelayan sebenar, gunakan HTTPS dan tetapkan cookie sesi sebagai Secure.

Berapa lama sesi patut kekal? Untuk kedai, dua jam munasabah. Untuk aplikasi kewangan, lebih pendek. Yang penting, ia tamat secara automatik, bukan kekal selamanya.

Boleh saya menambah peranan lain? Boleh. Lajur peranan sudah ada. Dalam bab keselamatan, kita akan menggunakannya untuk menghalang staf biasa daripada mengarkibkan produk.

Sumber

Log masuk untuk menyimpan kemajuan bacaan anda.

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk