Fayl yuklash — $_FILES, move_uploaded_file, xavfsizlik

~15 daq · PHP · PHP va Veb

Muammodan boshlaylik

Foydalanuvchi profil rasmini yuklashi kerak. Oddiy ko'rinadi: formada fayl tanlash maydoni, serverda saqlash. Lekin fayl yuklash — veb-saytlardagi eng xavfli funksiyalardan biri.

Tasavvur qiling: kimdir rasm o'rniga rasm.php faylini yukladi, ichida:

<?php system($_GET['cmd']);

Siz uni uploads/ papkaga saqladingiz. Endi hujumchi sayt.uz/uploads/rasm.php?cmd=... ni ochib, serveringizda istalgan buyruqni bajaradi. Butun sayt, baza, boshqa foydalanuvchilar ma'lumotlari — hammasi uning qo'lida.

Shuning uchun bu darsda faqat "qanday yuklash" emas, "qanday xavfsiz yuklash" ni o'rganamiz.


Forma

<form method="post" enctype="multipart/form-data">
    <input type="file" name="rasm" accept="image/*">
    <button>Yuklash</button>
</form>

Ikki shart:

  • method="post" — GET bilan fayl yuborib bo'lmaydi
  • enctype="multipart/form-data" — busiz fayl yuborilmaydi, faqat nomi keladi

$_FILES nima beradi

<?php

print_r($_FILES['rasm']);
Array
(
    [name] => mening-rasmim.jpg       ← foydalanuvchi kompyuteridagi nomi
    [full_path] => mening-rasmim.jpg
    [type] => image/jpeg              ← BRAUZER aytgan tur — ishonib bo'lmaydi!
    [tmp_name] => /tmp/phpA1b2C3      ← serverdagi vaqtinchalik fayl
    [error] => 0                      ← 0 = xato yo'q
    [size] => 245760                  ← hajmi baytlarda
)

PHP faylni avval vaqtinchalik papkaga saqlaydi. Skript tugashi bilan u o'chiriladi. Saqlab qolish uchun uni move_uploaded_file() bilan ko'chirish kerak.

name va type — foydalanuvchi yuborgan ma'lumot. Ikkalasini ham soxtalashtirish mumkin.


Xato kodlari

Kod Konstanta Ma'nosi
0 UPLOAD_ERR_OK Muvaffaqiyatli
1 UPLOAD_ERR_INI_SIZE php.ini dagi upload_max_filesize dan katta
2 UPLOAD_ERR_FORM_SIZE Formadagi MAX_FILE_SIZE dan katta
3 UPLOAD_ERR_PARTIAL Qisman yuklandi
4 UPLOAD_ERR_NO_FILE Fayl tanlanmagan

Xavfsiz yuklash — to'liq misol

<?php
declare(strict_types=1);

const MAKS_HAJM = 2 * 1024 * 1024;   // 2 MB
const RUXSAT = [
    'image/jpeg' => 'jpg',
    'image/png'  => 'png',
    'image/webp' => 'webp',
];

$xato = '';
$natija = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $fayl = $_FILES['rasm'] ?? null;

    if (!$fayl || $fayl['error'] === UPLOAD_ERR_NO_FILE) {
        $xato = "Fayl tanlanmagan";
    } elseif ($fayl['error'] !== UPLOAD_ERR_OK) {
        $xato = "Yuklashda xato (kod: {$fayl['error']})";
    } elseif ($fayl['size'] > MAKS_HAJM) {
        $xato = "Fayl 2 MB dan oshmasin";
    } else {
        // 1. Haqiqiy turini faylning MAZMUNIDAN aniqlash
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($fayl['tmp_name']);

        if (!isset(RUXSAT[$mime])) {
            $xato = "Faqat JPG, PNG yoki WEBP rasm";
        } else {
            // 2. Yangi, tasodifiy nom — foydalanuvchi bergan nomni ishlatmaymiz
            $kengaytma = RUXSAT[$mime];
            $yangiNom = bin2hex(random_bytes(16)) . '.' . $kengaytma;
            $manzil = __DIR__ . '/uploads/' . $yangiNom;

            // 3. Ko'chirish
            if (move_uploaded_file($fayl['tmp_name'], $manzil)) {
                $natija = 'uploads/' . $yangiNom;
            } else {
                $xato = "Faylni saqlab bo'lmadi";
            }
        }
    }
}
?>
<form method="post" enctype="multipart/form-data">
    <?php if ($xato): ?><p style="color:red"><?= htmlspecialchars($xato) ?></p><?php endif; ?>
    <?php if ($natija): ?><img src="<?= htmlspecialchars($natija) ?>" width="200"><?php endif; ?>
    <input type="file" name="rasm" accept="image/jpeg,image/png,image/webp">
    <button>Yuklash</button>
</form>

Xavfsizlik qoidalari — nima uchun har biri kerak

1. Turini finfo bilan aniqlang, $_FILES['type'] bilan emas. type ni brauzer yuboradi — hujumchi .php faylni image/jpeg deb yubora oladi. finfo esa faylning birinchi baytlarini ("sehrli raqamlar") o'qib, haqiqiy turini aniqlaydi.

2. Kengaytmani o'zingiz belgilang. Foydalanuvchi nomi rasm.jpg.php yoki rasm.php%00.jpg bo'lishi mumkin. Biz kengaytmani aniqlangan MIME turidan olamiz — foydalanuvchi nomidan emas.

3. Nomni tasodifiy yarating. random_bytes(16) — taxmin qilib bo'lmaydigan nom. Bu ham boshqa fayllarni ustidan yozib yuborishdan (../index.php), ham boshqa foydalanuvchilarning fayllarini sanab chiqishdan himoya qiladi. Asl nomni kerak bo'lsa bazada saqlang.

4. move_uploaded_file ishlating, rename yoki copy emas. U faylning haqiqatan ham HTTP orqali yuklanganini tekshiradi.

5. Yuklash papkasida PHP bajarilmasin. Eng yaxshisi — uploads/ ni umuman veb-ildizdan tashqarida saqlash va fayllarni PHP skript orqali berish. Agar ichida bo'lsa, Apache uchun uploads/.htaccess:

php_flag engine off
<FilesMatch "\.(php|phtml|phar)
quot;> Require all denied </FilesMatch>

Nginx da bu location bloki orqali sozlanadi.


Hajm cheklovlari

PHP ning o'z cheklovlari bor (php.ini):

upload_max_filesize = 2M     ← bitta fayl
post_max_size = 8M           ← butun so'rov (barcha fayllar + maydonlar)
max_file_uploads = 20        ← bir so'rovda nechta fayl

[!WARNING] Agar so'rov post_max_size dan katta bo'lsa — PHP $_POST va $_FILES ni butunlay bo'sh qoldiradi, hech qanday xato kodi bermaydi. "Forma ishlamayapti" degan sirli xatolarning tez-tez uchraydigan sababi.


Bir nechta fayl

<input type="file" name="rasmlar[]" multiple>

$_FILES bu holatda noqulay tuzilmada keladi — har bir xususiyat alohida massiv:

<?php

// $_FILES['rasmlar']['name'][0], $_FILES['rasmlar']['name'][1], ...
$soni = count($_FILES['rasmlar']['name']);

for ($i = 0; $i < $soni; $i++) {
    $fayl = [
        'name'     => $_FILES['rasmlar']['name'][$i],
        'tmp_name' => $_FILES['rasmlar']['tmp_name'][$i],
        'error'    => $_FILES['rasmlar']['error'][$i],
        'size'     => $_FILES['rasmlar']['size'][$i],
    ];
    // har birini yuqoridagi kabi tekshirish va saqlash
}

Nima uchun shunday?

Nima uchun PHP faylni avval vaqtinchalik papkaga saqlaydi, to'g'ridan-to'g'ri kerakli joyga emas?

Chunki PHP sizning skriptingiz ishga tushishidan oldin butun so'rovni qabul qilib oladi. Bu vaqtda u hali faylni qayerga saqlashni bilmaydi — bu sizning kodingizda yozilgan. Bundan tashqari, vaqtinchalik saqlash xavfsizlik chegarasi: tekshiruvdan o'tmagan fayl hech qachon doimiy joyga tushmaydi va skript tugashi bilan o'z-o'zidan o'chadi.


Xulosa

Eslab qoling:

  • Formada method="post" va enctype="multipart/form-data" shart
  • $_FILES['x']['error'] ni birinchi tekshiring
  • Turini finfo bilan aniqlang; name va type ga ishonmang
  • Kengaytma va nomni o'zingiz yarating: bin2hex(random_bytes(16))
  • move_uploaded_file() bilan ko'chiring
  • Yuklash papkasida PHP bajarilmasin — yaxshisi veb-ildizdan tashqarida

Keyingi dars

23-dars: header(), redirect, HTTP status kodlar, JSON javob