Tutorial Pasukan Lucee MY

Sahkan fail muat naik dalam CFML: apa yang accept benar-benar semak

Diukur di Lucee 7.1.1.17: accept menyemak MIME kandungan, strict=false membuka pintu, fail kosong dan polyglot lulus, dan senarai blok lalai tiada phtml.

Sahkan fail muat naik dalam CFML: apa yang accept benar-benar semak
0
Kandungan

Sahkan fail muat naik dalam CFML: apa yang accept benar-benar semak

Jawapan pantas: Dalam Lucee 7.1, atribut accept pada cffile dan fileUpload menyemak MIME yang disimpul daripada kandungan fail, bukan sambungan namanya, jadi skrip PHP yang dinamakan .jpg memang ditolak. Tetapi semakan itu hanya membaca pengepala fail: fail kosong diterima, polyglot bertanda GIF atau PNG lulus semakan awal, dan sambungan seperti phtml, exe atau js tiada dalam senarai blok lalai enjin. Pengesahan yang boleh dipercayai memerlukan empat lapis: senarai putih sambungan, semakan saiz sendiri, dekod imej penuh, dan tulis semula imej ke nama baharu.

Setiap angka di bawah diukur pada rig Lucee 7.1.1.17-RC, dengan fail contoh dihantar ke pengendali cffile sebenar melalui permintaan multipart.

Apa yang accept sebenarnya semak: sambungan atau kandungan?

Kandungan. Skrip PHP bernama shell.php.jpg ditolak kerana MIME yang dikesan ialah text/x-php, walaupun sambungannya .jpg:

The MIME type of the uploaded file [text/x-php] was rejected by the server.
Only the following type(s) are allowed, [image/jpeg,image/png].

Fail tanpa sambungan tetapi berisi JPEG yang sah pula diterima, kerana nama fail tidak dibaca untuk keputusan itu:

Fail dihantarIsi sebenarKeputusan accept
gambar.jpgJPEG sahDITERIMA
tiada-sambunganJPEG tanpa sambunganDITERIMA
shell.php.jpgKod PHPDITOLAK, text/x-php
polyglot.php.jpgGIF89a + kod PHPDITOLAK, image/gif
spoof.jpgTeks biasaDITOLAK, text/plain
kosong.jpgSifar baitDITERIMA

Baris terakhir itu menentukan reka bentuk anda. Enjin tidak menganggap fail sifar bait sebagai ralat, jadi semakan saiz minimum mesti dibuat sendiri.

Bila strict="false" mematikan semakan kandungan

Dokumentasi cffile menyatakan strict bernilai benar secara lalai untuk tindakan upload, dan hanya MIME disokong dalam accept selagi nilai itu benar. Apabila ditukar kepada palsu, accept menerima sambungan: dengan accept="jpg,png" dan strict="false", teks bernama spoof.jpg DITERIMA, dan skrip shell.php.jpg juga DITERIMA. Satu baris konfigurasi membatalkan seluruh semakan kandungan.

Adakah fileGetMimeType() mencukupi untuk mengesahkan imej?

Tidak, kerana ia membaca pengepala dan bukan keseluruhan fail. Blok ini membina empat fail contoh, kemudian mencetak apa yang enjin lihat:

<cfscript>
    folder = "/tmp/uji-sahkan";
    if (!directoryExists(folder)) {
        directoryCreate(folder);
    }
    imej = imageNew("", 4, 4, "rgb");
    imageWrite(imej, folder & "/gambar.jpg", 0.9);
    fileWrite(folder & "/spoof.jpg", "ini teks biasa, bukan imej");
    fileWrite(folder & "/skrip.php.jpg", "<?php echo 1; ?>");
    fileWrite(folder & "/polyglot.php.jpg", "GIF89a<?php echo 2; ?>");

    baris = "";
    senarai = ["gambar.jpg", "spoof.jpg", "skrip.php.jpg", "polyglot.php.jpg"];
    for (f in senarai) {
        laluan = folder & "/" & f;
        mime = "";
        try {
            mime = fileGetMimeType(laluan);
        } catch (any e) {
            mime = "ralat: " & e.message;
        }
        baris = baris & "<tr><td>" & f & "</td><td>" & mime & "</td><td>" & (isImageFile(laluan) ? "ya" : "tidak") & "</td></tr>";
    }
</cfscript>
<cfcontent type="text/html; charset=utf-8">
<cfoutput>
<table border="1">
<tr><th>fail</th><th>fileGetMimeType</th><th>isImageFile</th></tr>
#baris#
</table>
</cfoutput>

Keluarannya: gambar.jpg menjadi image/jpeg dan ya, manakala polyglot.php.jpg menjadi image/gif tetapi tidak. Baris terakhir itu yang penting: MIME percaya pengepala, isImageFile() cuba menyahkod keseluruhan fail. Pengepala boleh dipalsukan dengan beberapa bait, struktur imej tidak.

Sambungan mana yang disekat Lucee secara lalai?

Enjin membawa senarai blok sendiri, dan senarai itu tidak lengkap. Satu JPEG sah disalin kepada dua puluh sambungan, kemudian setiap satu dihantar dengan accept="image/jpeg,image/png", jadi satu-satunya pemboleh ubah ialah nama:

Ditolak enjinDiterima enjin
asp, aspx, cfc, cfm, cfml, htm, html, jsp, jspx, phpbat, class, exe, js, phtml, pl, py, sh, svg, war

Mesej untuk cfm ialah Upload of files with extension [cfm] is not permitted, dan perinciannya menamakan allowedExtension, blockedExtension, sifat this.blockedExtForFileUpload serta sistem sifat lucee.upload.blocklist. Dua perkara perlu diingat: phtml tiada dalam senarai lalai walaupun banyak pemasangan PHP menghidangkannya sebagai skrip, dan penolakan itu berlaku sebelum semakan MIME, jadi fail .cfm ditolak walaupun kandungannya imej sah.

Adakah allowedExtension menutup semua jurang?

Sebahagian besar, bukan semua. Dengan allowedExtension="jpg,jpeg,png", kesemua sepuluh sambungan di atas ditolak, dan mesejnya menyebut senarai putih itu: only allows the following extensions in this context [jpg,jpeg,png]. Tetapi fail tanpa sambungan masih diterima kerana tiada sambungan untuk dibandingkan, dan fail sifar bait juga masih diterima. Dokumentasi menyatakan atribut ini mengatasi blockedExtensions dan tetapan Application.cfc, jadi jangan harapkan tetapan lain membetulkan senarai yang terlalu luas.

Berapa besar fail yang diterima sebelum ditolak?

Tiada had saiz dalam cffile. Fail 6 MB dan 12 MB dua-dua DITERIMA dan ditulis ke cakera dengan mod 0640 sebelum sebarang kod aplikasi berjalan. Had mesti diletakkan di proksi hadapan dan diperiksa semula melalui fileSize, tetapi ingat susunannya: apabila kod memeriksa saiz, fail itu sudah menggunakan cakera. Pemeriksaan selepas muat naik menghalang fail daripada kekal, bukan daripada mendarat.

Apa yang berlaku pada nama fail daripada klien?

Tiga tingkah laku berbeza, semuanya diukur. nameConflict="makeunique" hanya menukar nama apabila berlaku perlanggaran: muat naik kedua menyimpan gambar-mvpv39e7_4.jpg dengan fileexisted bernilai true. Laluan traversal dibuang: fail bernama ../../luar.jpg disimpan sebagai luar.jpg dalam destinasi. Tetapi aksara HTML kekal: gambar "saya" <b>.jpg disimpan sebagai gambar %22saya%22 <b>.jpg, jadi nama itu mesti dienkod dengan encodeForHTML() semasa dipaparkan, seperti dalam panduan keselamatan XSS dan CSRF. Direktori destinasi pula mesti wujud; cffile tidak menciptanya.

Saluran pengesahan yang lengkap

Gabungan empat lapisan ini diuji, dan setiap cawangan penolakan pernah dicapai. Fail masuk ke karantina di luar webroot, diperiksa, kemudian ditulis semula ke storan dengan nama yang dijana sendiri dan sambungan tetap:

<cfscript>
    karantina = "/tmp/muat-karantina";
    storan = "/tmp/muat-storan";
    if (!directoryExists(karantina)) { directoryCreate(karantina); }
    if (!directoryExists(storan)) { directoryCreate(storan); }

    hasil = { ok = false, sebab = "", nama = "", saiz = 0 };

    try {
        u = fileUpload(karantina, "fail", "image/jpeg,image/png", "makeunique", true, "jpg,jpeg,png");
        if (u.fileSize LTE 0) {
            hasil.sebab = "Fail kosong ditolak";
        } else if (u.fileSize GT 2097152) {
            hasil.sebab = "Melebihi had 2 MB";
        } else if (!isImageFile(u.serverDirectory & "/" & u.serverFile)) {
            hasil.sebab = "Tidak boleh dinyahkod sebagai imej";
        } else {
            imej = imageRead(u.serverDirectory & "/" & u.serverFile);
            namaBaharu = lcase(replace(createUUID(), "-", "", "all")) & ".jpg";
            imageWrite(imej, storan & "/" & namaBaharu, 0.85, true);
            hasil.ok = true;
            hasil.nama = namaBaharu;
            hasil.saiz = getFileInfo(storan & "/" & namaBaharu).size;
        }
        fileDelete(u.serverDirectory & "/" & u.serverFile);
    } catch (any e) {
        hasil.sebab = e.message;
    }
</cfscript>
<cfcontent type="application/json; charset=utf-8">
<cfoutput>#serializeJSON(hasil)#</cfoutput>

Susunan argumen fileUpload() diambil daripada dokumentasi fungsi itu: destinasi, medan fail, accept, nameConflict, strict, allowedExtensions. Jawapan yang direkodkan pada rig:

Fail dihantarJawapan
JPEG sah, JPEG dengan kod ditampal, JPEG tanpa sambungan{"SAIZ":333,"OK":true,...}
Sifar baitFail kosong ditolak
Teks bernama .jpg atau polyglot GIFrejected oleh semakan MIME
.phtml dengan kandungan JPEGextension [phtml] is not permitted
12 MBMelebihi had 2 MB

Tiga kes pertama memberi SAIZ yang sama kerana semuanya melalui imageWrite(), dan sambungan fail yang disimpan sentiasa jpg kerana kod yang menetapkannya.

Adakah tulis semula imej benar-benar membuang kod?

Ya, dan penanda membuktikannya. JPEG sah ditulis, kod PHP ditampal di hujungnya, kemudian fail dibaca dengan imageRead() dan ditulis semula dengan imageWrite():

<cfscript>
    folder = "/tmp/uji-tulis-semula";
    if (!directoryExists(folder)) { directoryCreate(folder); }
    imej = imageNew("", 8, 8, "rgb");
    imageWrite(imej, folder & "/asal.jpg", 0.9);
    fileAppend(folder & "/asal.jpg", chr(10) & "<?php echo 'PENANDA'; ?>");

    bersih = imageRead(folder & "/asal.jpg");
    imageWrite(bersih, folder & "/bersih.jpg", 0.85, true);

    adaAsal = find("PENANDA", toString(fileReadBinary(folder & "/asal.jpg"))) GT 0;
    adaBersih = find("PENANDA", toString(fileReadBinary(folder & "/bersih.jpg"))) GT 0;
    saizAsal = getFileInfo(folder & "/asal.jpg").size;
    saizBersih = getFileInfo(folder & "/bersih.jpg").size;
</cfscript>
<cfcontent type="text/html; charset=utf-8">
<cfoutput><p>asal #saizAsal# bait, penanda #adaAsal ? "ADA" : "tiada"#; simpanan #saizBersih# bait, penanda #adaBersih ? "ADA" : "tiada"#</p></cfoutput>

Keluarannya ialah asal 656 bait, penanda ADA; simpanan 631 bait, penanda tiada. Ini menjelaskan had setiap lapisan dengan jujur: isImageFile() tidak menangkap polyglot jenis ini, kerana JPEG dengan kod tambahan masih boleh dinyahkod. Yang membuangnya ialah penulisan semula, manakala polyglot GIF atau PNG ditangkap lebih awal kerana strukturnya rosak.

Di mana fail patut disimpan?

Di luar webroot, atau dalam webroot dengan nama yang anda jana sendiri dan sambungan yang anda tetapkan selepas pengesahan. Sebabnya ada dalam jadual di atas: phtml, exe, sh dan js tidak disekat secara lalai, dan apa yang boleh dilaksanakan bergantung pada pelayan yang menghidangkan direktori itu. Kawalan lain dibincangkan dalam panduan muat naik fail yang selamat dan artikel kebenaran fail dan muat naik, manakala asas tag itu sendiri ada dalam panduan cffile. Untuk pemprosesan selepas muat naik, lihat cfimage untuk watermark dan thumbnail.

Jadual ringkasan kawalan

KawalanDitangkapTerlepas
accept (lalai)Skrip atau teks yang dinamakan semula, SVGFail kosong, polyglot bertanda imej
Senarai blok lalaicfm, cfc, php, jsp, htmlphtml, exe, sh, js, svg
allowedExtensionSambungan di luar senarai putihFail tanpa sambungan, fail kosong
Semakan fileSizeSifar bait, fail terlalu besarCakera sudah digunakan sebelum semakan
isImageFile()Polyglot GIF atau PNG yang rosakJPEG dengan kod di hujung
imageWrite() semulaKod tambahan, nama klienFail bukan imej langsung

Soalan Lazim

Adakah menyemak sambungan fail mencukupi untuk pengesahan muat naik? Tidak. accept menyemak MIME kandungan, tetapi sambungan tetap perlu sebagai lapisan kedua kerana senarai blok lalai tiada phtml, exe atau js. Guna accept dan allowedExtension bersama, kemudian tetapkan sendiri sambungan fail yang disimpan.

Kenapa fail kosong lulus semakan accept? Kerana tiada kandungan untuk diperiksa, dan enjin tidak menganggap saiz sifar sebagai ralat. Fail sifar bait diterima dengan fileSize bernilai 0. Tambah semakan fileSize LTE 0 dalam kod anda.

Boleh saya bergantung pada fileGetMimeType() untuk mengesahkan imej? Boleh untuk semakan pantas, bukan sebagai keputusan muktamad. Fungsi itu membaca pengepala, jadi fail dengan tandatangan GIF diikuti kod PHP dilaporkan sebagai image/gif. isImageFile() lebih ketat kerana ia menyahkod fail, dan penulisan semula yang benar-benar membuang kod tambahan.

Kenapa fail .cfm ditolak walaupun kandungannya imej yang sah? Kerana Lucee menyemak senarai blok sambungan sebelum semakan MIME, dan mesejnya Upload of files with extension [cfm] is not permitted. Senarai itu boleh diubah melalui blockedExtension atau lucee.upload.blocklist, tetapi jangan longgarkannya untuk sambungan yang boleh dilaksanakan.

Adakah Lucee menghadkan saiz fail yang dimuat naik? Tidak melalui cffile. Fail 12 MB diterima dan ditulis ke cakera dalam ujian ini. Had saiz mesti diletakkan di proksi hadapan dan diperiksa semula melalui fileSize.

Sumber

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk