Tutorial Pasukan Lucee MY

Keselamatan: XSS, CSRF, peranan dan had cubaan

Tutup empat lubang keselamatan dalam aplikasi CFML anda: suntikan skrip pada output, token borang CSRF, semakan peranan di pelayan, dan had cubaan log masuk.

Keselamatan: XSS, CSRF, peranan dan had cubaan
0
Kandungan

Keselamatan: XSS, CSRF, peranan dan had cubaan

Bab ini bermula dengan penemuan yang tidak menyenangkan.

Semasa menguji bab ini, saya memasukkan satu produk dengan nama:

<b>Tepung</b> <script>alert(1)</script>

Halaman senarai memaparkannya sebagai HTML sebenar. Tag <script> muncul dalam kod sumber halaman, dan pelayar akan menjalankannya. Dalam erti kata lain, sesiapa yang boleh memasukkan nama produk boleh menjalankan skrip dalam pelayar pelawat lain.

Itu dipanggil XSS tersimpan, dan ia ada dalam kod yang saya tulis sendiri dalam bab-bab sebelum ini, termasuk senarai produk dan halaman gerakan stok.

Mengapa ia berlaku

Kod lama kita memaparkan nilai terus:

<cfoutput>
    <td>#nama#</td>
</cfoutput>

CFML tidak tahu sama ada nama mengandungi teks biasa atau HTML. Ia mencetak apa sahaja yang ada. Apabila nilai itu mengandungi <script>, pelayar menerimanya sebagai arahan.

Penyelesaiannya satu fungsi: encodeForHTML.

<cfoutput>
    <td>#encodeForHTML(nama)#</td>
</cfoutput>

Selepas perubahan itu, ujian yang sama memulangkan sifar tag <script>, dan nama produk dipaparkan sebagai teks:

<b>Tepung</b> <script>alert(1)</script>

Pengguna nampak tanda kurung itu, bukan kesan tebal atau kotak dialog.

Peraturan output

Ada dua fungsi, dan anda perlu memilih yang betul mengikut tempat nilai itu muncul.

Tempat nilai munculFungsi
Di antara tag HTMLencodeForHTML
Di dalam atribut value, hrefencodeForHTMLAttribute
Di dalam JavaScriptencodeForJavaScript
Di dalam SQLcfqueryparam

Kesilapan biasa ialah menggunakan encodeForHTML untuk nilai di dalam atribut. Ia berfungsi untuk kebanyakan kes, tetapi encodeForHTMLAttribute lebih tepat kerana ia mengendalikan petikan dengan betul.

Satu perkara yang perlu difahami: XSS berlaku pada output, bukan input. Anda boleh cuba menapis nama produk semasa menyimpan, tetapi penapis sentiasa ada jalan keluar. Melarikan nilai semasa memaparkan lebih pasti, kerana anda tahu konteksnya di situ.

Sebab itu peraturan ini dipegang di setiap halaman, bukan di satu tempat sahaja. Ia juga sebabnya bab ini muncul selepas semua halaman siap: lebih mudah menyemak keseluruhan kod apabila ia sudah wujud.

Token CSRF

Lubang kedua ialah permintaan palsu daripada laman lain. Borang yang terdedah ialah yang anda bina dalam bab borang. Kalau anda sedang log masuk, laman jahat boleh menghantar borang ke produk-arkib.cfm tanpa pengetahuan anda, dan pelayar akan membawa cookie sesi anda bersama.

Token CSRF menghalangnya. Kita menjana satu nilai rawak setiap sesi, meletakkannya dalam borang, dan menyemaknya apabila borang dihantar.

Fail includes/csrf.cfm:

<cfif NOT structKeyExists(session, "csrf")>
    <cfset session.csrf = createUUID()>
</cfif>

<cfif cgi.request_method EQ "POST">
    <cfif NOT structKeyExists(form, "csrf") OR form.csrf NEQ session.csrf>
        <cfabort showerror="Token keselamatan tidak sah. Muat semula halaman dan cuba lagi.">
    </cfif>
</cfif>

Kemudian setiap borang mendapat satu medan tersembunyi:

<input type="hidden" name="csrf" value="#session.csrf#">

Dan setiap halaman yang menerima POST memanggil include itu sebelum apa-apa logik:

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

Saya menguji dua kes. POST tanpa token membalas 500 dengan mesej "Token keselamatan tidak sah". POST dengan token yang sah menyimpan gerakan stok seperti biasa.

Perhatikan bahawa token itu dijana semula selepas log masuk berjaya. Itu mengelakkan token lama daripada digunakan semula selepas sesi baharu bermula.

Semakan peranan

Lajur peranan sudah ada dalam jadual pengguna sejak bab log masuk, tetapi ia belum digunakan. Sekarang kita menggunakannya.

Halaman arkib menyemak peranan sebelum melakukan apa-apa:

<cfif session.pengguna.peranan NEQ "admin">
    <h2>Tidak dibenarkan</h2>
    <p class="butiran">Hanya pengguna dengan peranan admin boleh mengarkibkan produk.</p>
    <cfabort>
</cfif>

Saya mencipta seorang pengguna staf untuk menguji ini. Apabila staf cuba mengarkibkan produk, halaman memaparkan "Tidak dibenarkan" dan produk itu kekal aktif. Apabila admin melakukannya, produk itu diarkibkan dan halaman melencong ke senarai.

Peraturan penting: semakan peranan mesti berlaku di pelayan. Menyembunyikan butang di antara muka tidak menghalang sesiapa daripada menaip alamat terus.

Had cubaan log masuk

Lubang ketiga ialah cubaan kata laluan tanpa had. PBKDF2 melambatkan setiap cubaan, tetapi ia tidak menghentikan cubaan berulang.

Kita menambah satu jadual kecil:

CREATE TABLE cubaan_log_masuk (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nama_pengguna VARCHAR(60) NOT NULL,
    ip VARCHAR(45) NOT NULL,
    berjaya TINYINT(1) NOT NULL DEFAULT 0,
    masa DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_cubaan (nama_pengguna, masa)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Sebelum mengesahkan kata laluan, halaman mengira cubaan gagal dalam 15 minit terakhir:

<cfquery name="kiraCubaan" datasource="inventori">
    SELECT COUNT(*) AS bilangan
    FROM cubaan_log_masuk
    WHERE nama_pengguna = <cfqueryparam value="#namaPengguna#" cfsqltype="cf_sql_varchar">
      AND ip = <cfqueryparam value="#ipPelawat#" cfsqltype="cf_sql_varchar">
      AND berjaya = 0
      AND masa > DATE_SUB(NOW(), INTERVAL 15 MINUTE)
</cfquery>

<cfif kiraCubaan.bilangan GTE 5>
    <cfset ralat = "Terlalu banyak cubaan. Cuba lagi selepas 15 minit.">
</cfif>

Saya menguji tepat lima cubaan salah, kemudian satu cubaan dengan kata laluan betul. Cubaan keenam tetap ditolak dengan mesej had, walaupun kata laluannya sah.

Itu memang tujuannya. Kekurangannya, pengguna sah yang tersilap taip lima kali perlu menunggu. Untuk kedai kecil, 15 minit munasabah. Untuk aplikasi yang lebih besar, gunakan CAPTCHA atau pengesahan dua langkah sebagai ganti.

Halaman arkib menolak pengguna dengan peranan staf

Hasil sebenar pada rig ujian kami: semakan peranan berlaku di pelayan, bukan sekadar menyembunyikan butang.

Perangkap tanda pagar, sekali lagi

Dua kali dalam bab ini kod saya gagal dikompil kerana warna CSS di dalam <cfoutput>:

<form ... style="background:#fff7ed">
<button ... style="background:#b45309">

CFML membaca #fff7ed;margin-top: sebagai ungkapan dan mengadu "missing terminating [#]". Penyelesaiannya, seperti dalam bab stok: pindahkan warna itu ke kelas CSS di luar <cfoutput>.

Ini kali kelima tanda pagar menyebabkan masalah dalam siri ini. Ia bukan kebetulan: # mempunyai makna khas dalam CFML, dan warna hex kebetulan menggunakannya.

Senarai semak

Sebelum anda meletakkan aplikasi ini pada pelayan sebenar, pastikan setiap baris ini benar.

PerkaraStatus dalam siri ini
Setiap output menggunakan encodeForHTMLYa, sejak bab ini
Setiap input SQL melalui cfqueryparamYa, sejak bab borang
Setiap borang POST mempunyai token CSRFYa, empat borang
Peranan disemak di pelayanYa, halaman arkib
Cubaan log masuk dihadkanYa, lima cubaan setiap 15 minit
Kata laluan disimpan dengan PBKDF2 dan garamYa, sejak bab log masuk
HTTPS diaktifkanDi pelayan sebenar, ya. Di WampServer, tidak

Baris terakhir itu penting. Di komputer sendiri, HTTPS tidak diperlukan. Apabila aplikasi ini diletakkan pada pelayan sebenar, sijil SSL dan lencongan paksa ke HTTPS wajib, kerana cookie sesi boleh dicuri melalui rangkaian terbuka.

Apa yang masih belum ada

Tiada sistem yang lengkap selepas satu bab. Tiga perkara yang sengaja ditinggalkan:

  • Pengesahan dua langkah. Berguna, tetapi menambah kerumitan yang tidak perlu untuk kedai kecil.
  • Log audit penuh. Kita menyimpan gerakan stok, tetapi bukan setiap perubahan harga.
  • Sandaran automatik. Ia perkara pelayan, bukan aplikasi, dan akan dibincangkan dalam bab terakhir.

Soalan Lazim

Kenapa tidak simpan token CSRF dalam cookie sahaja? Kerana cookie dihantar automatik oleh pelayar. Token yang hanya ada dalam borang membuktikan bahawa permintaan itu datang daripada halaman kita, bukan daripada laman lain.

Adakah token CSRF perlu berubah setiap permintaan? Tidak wajib. Satu token setiap sesi sudah cukup untuk menghalang serangan biasa. Menukarnya setiap permintaan menyukarkan penggunaan dengan banyak tab.

Boleh saya menggunakan semakan peranan dalam menu sahaja? Tidak. Menu hanya mempengaruhi apa yang dilihat. Pelayan mesti menyemak setiap permintaan.

Adakah had cubaan menyekat pengguna yang sah? Boleh, jika mereka tersilap taip berulang kali. Sebab itu had ini dikira mengikut nama pengguna dan IP, dan tamat selepas 15 minit.

Bagaimana kalau penyerang menukar IP? Had per-IP boleh dipintas. Untuk aplikasi besar, tambah had per-akaun dan pemantauan corak. Untuk kedai kecil, gabungan kedua-duanya sudah memadai.

Sumber

Log masuk untuk menyimpan kemajuan bacaan anda.

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk