Full Secure Image Upload Script

Nie wiem, czy do tego dojdzie, ale spróbuję.

Przez ostatnią godzinę badałem bezpieczeństwo przesyłania obrazów. Dowiedziałem się, że istnieje wiele funkcji do testowania uploadu.

W moim projekcie muszę być bezpieczny z przesłanymi obrazami. Może być ich naprawdę dużo i może wymagać dużej przepustowości, więc kupowanie API nie jest opcją.

Więc postanowiłem uzyskać pełny skrypt PHP do naprawdę bezpiecznego przesyłania obrazów. Myślę również, że pomoże to wielu z ludzi, bo nie da się znaleźć naprawdę bezpiecznego Ale nie jestem ekspertem w php, więc to naprawdę ból głowy dla mnie, aby dodać kilka funkcji, więc będę prosić o pomoc tej społeczności, aby stworzyć jeden pełny skrypt naprawdę bezpiecznego uploadu obrazu.

Naprawdę świetne tematy na ten temat są tutaj (jednak po prostu mówią, co jest potrzebne ,aby zrobić trick, ale nie jak to zrobić, a jak powiedziałem, nie jestem mistrzem w PHP, więc nie jestem w stanie zrobić tego wszystkiego sam): PHP image upload lista kontroli bezpieczeństwa https://security.stackexchange.com/questions/32852/risks-of-a-php-image-upload-form

Podsumowując, mówią, że to jest to, co jest potrzebne do załadowania obrazu bezpieczeństwa (zacytuję z powyższych stron):

  • Wyłącz PHP z uruchamiania wewnątrz folderu upload za pomocą .httaccess.
  • Nie zezwalaj na przesyłanie, jeśli nazwa pliku zawiera ciąg znaków "php".
  • Zezwalaj tylko na Rozszerzenia: jpg,jpeg, gif i png.
  • Zezwalaj tylko na typ pliku obrazu.
  • Wyłącz obraz z dwoma typami plików.
  • Zmień nazwę obrazu. Prześlij do podkatalogu, a nie katalogu głównego.

Także:

  • ponownie przetworz obraz za pomocą GD (lub Imagick) i zapisz przetworzony obraz. Wszystkie inne są po prostu nudne dla hakerów]}
  • jak zauważył rr, użyj move_uploaded_file () dla dowolnego uploadu"
  • przy okazji, chciałbyś być bardzo restrykcyjny co do twojego uploadu folder. Miejsca te są jednym z ciemnych zakątków, gdzie wiele wyczynów [41]} zdarza się. Jest to ważne dla każdego typu przesyłania i dowolnego programowania
    język / serwer. Sprawdź
    https://www.owasp.org/index.php/Unrestricted_File_Upload
  • Poziom 1: Sprawdź rozszerzenie (plik rozszerzenia kończy się na)
  • Poziom 2: Sprawdź typ MIME ($file_info = getimagesize ($_FILES ['image_file']); $file_mime = $file_info ['mime'])
  • Poziom 3: odczytaj pierwsze 100 bajtów i sprawdź, czy mają jakieś bajty w następującym zakresie: ASCII 0-8, 12-31 (dziesiętne).
  • Poziom 4: Sprawdź magiczne liczby w nagłówku (pierwsze 10-20 bajtów pliku). Można znaleźć niektóre bajty nagłówków plików z proszę.:
    http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Examples
  • Możesz również uruchomić "is_uploaded_file" na $_files ['my_files'] ['tmp_name']. Zobacz też
    http://php.net/manual/en/function.is-uploaded-file.php
Tutaj jest duża część, ale to jeszcze nie wszystko. (Jeśli wiesz coś więcej, co może pomóc, aby upload był jeszcze bezpieczniejszy, podziel się.)

THIS IS WHAT WE GOT NOW

  • Main PHP:

    function uploadFile ($file_field = null, $check_image = false, $random_name = false) {
    
    //Config Section    
    //Set file upload path
    $path = 'uploads/'; //with trailing slash
    //Set max file size in bytes
    $max_size = 1000000;
    //Set default file extension whitelist
    $whitelist_ext = array('jpeg','jpg','png','gif');
    //Set default file type whitelist
    $whitelist_type = array('image/jpeg', 'image/jpg', 'image/png','image/gif');
    
    //The Validation
    // Create an array to hold any output
    $out = array('error'=>null);
    
    if (!$file_field) {
      $out['error'][] = "Please specify a valid form field name";           
    }
    
    if (!$path) {
      $out['error'][] = "Please specify a valid upload path";               
    }
    
    if (count($out['error'])>0) {
      return $out;
    }
    
    //Make sure that there is a file
    if((!empty($_FILES[$file_field])) && ($_FILES[$file_field]['error'] == 0)) {
    
    // Get filename
    $file_info = pathinfo($_FILES[$file_field]['name']);
    $name = $file_info['filename'];
    $ext = $file_info['extension'];
    
    //Check file has the right extension           
    if (!in_array($ext, $whitelist_ext)) {
      $out['error'][] = "Invalid file Extension";
    }
    
    //Check that the file is of the right type
    if (!in_array($_FILES[$file_field]["type"], $whitelist_type)) {
      $out['error'][] = "Invalid file Type";
    }
    
    //Check that the file is not too big
    if ($_FILES[$file_field]["size"] > $max_size) {
      $out['error'][] = "File is too big";
    }
    
    //If $check image is set as true
    if ($check_image) {
      if (!getimagesize($_FILES[$file_field]['tmp_name'])) {
        $out['error'][] = "Uploaded file is not a valid image";
      }
    }
    
    //Create full filename including path
    if ($random_name) {
      // Generate random filename
      $tmp = str_replace(array('.',' '), array('',''), microtime());
    
      if (!$tmp || $tmp == '') {
        $out['error'][] = "File must have a name";
      }     
      $newname = $tmp.'.'.$ext;                                
    } else {
        $newname = $name.'.'.$ext;
    }
    
    //Check if file already exists on server
    if (file_exists($path.$newname)) {
      $out['error'][] = "A file with this name already exists";
    }
    
    if (count($out['error'])>0) {
      //The file has not correctly validated
      return $out;
    } 
    
    if (move_uploaded_file($_FILES[$file_field]['tmp_name'], $path.$newname)) {
      //Success
      $out['filepath'] = $path;
      $out['filename'] = $newname;
      return $out;
    } else {
      $out['error'][] = "Server Error!";
    }
    
     } else {
      $out['error'][] = "No file uploaded";
      return $out;
     }      
    }
    
    
    if (isset($_POST['submit'])) {
     $file = uploadFile('file', true, true);
     if (is_array($file['error'])) {
      $message = '';
      foreach ($file['error'] as $msg) {
      $message .= '<p>'.$msg.'</p>';    
     }
    } else {
     $message = "File uploaded successfully".$newname;
    }
     echo $message;
    }
    
  • I postać:

    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data" name="form1" id="form1">
    <input name="file" type="file" id="imagee" />
    <input name="submit" type="submit" value="Upload" />
    </form>
    

Więc proszę o pomoc poprzez zamieszczanie fragmentów kodów, które mi pomogą (i wszyscy inni), aby ten skrypt przesyłania obrazu, aby uczynić super bezpiecznym. Lub udostępniając / tworząc pełny skrypt ze wszystkimi dodanymi fragmentami.

Author: Community, 2016-07-21

2 answers

Kiedy zaczniesz pracować nad bezpiecznym skryptem przesyłania obrazów, jest wiele rzeczy do rozważenia. Teraz nie jestem ekspertem w tej dziedzinie, ale poproszono mnie o rozwinięcie tego raz w przeszłości. Przejdę przez cały proces, przez który tu przeszedłem, żebyś mógł go śledzić. W tym celu zacznę od bardzo podstawowego formularza html i skryptu php, który obsługuje pliki.

Formularz HTML:

<form name="upload" action="upload.php" method="POST" enctype="multipart/form-data">
    Select image to upload: <input type="file" name="image">
    <input type="submit" name="upload" value="upload">
</form>

Plik PHP:

<?php
$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?> 

Pierwszy problem: plik typy
Atakujący nie muszą korzystać z formularza na twojej stronie, aby przesłać pliki na twój serwer. Żądania POST mogą być przechwytywane na wiele sposobów. Pomyśl o dodatkach do przeglądarki, proxy, skryptach Perla. Bez względu na to, jak bardzo się staramy, nie możemy uniemożliwić atakującemu przesłania czegoś, czego nie powinien. Więc wszystkie nasze zabezpieczenia muszą być wykonane na serwerze.

Pierwszy problem to typy plików. W skrypcie powyżej atakujący może przesłać wszystko, co chce, np. php skrypt na przykład, i postępuj zgodnie z bezpośrednim linkiem, aby go wykonać. Aby temu zapobiec, wdrażamy weryfikacja typu treści :

<?php
if($_FILES['image']['type'] != "image/png") {
    echo "Only PNG images are allowed!";
    exit;
}

$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?>

Niestety to nie wystarczy. Jak wspomniałem wcześniej, atakujący ma pełną kontrolę nad żądaniem. Nic nie powstrzyma go przed modyfikacją nagłówków żądań i po prostu zmień typ treści na "image / png". Więc zamiast polegać tylko na nagłówku Content-type, lepiej byłoby również zweryfikować zawartość przesłanego pliku. Tutaj przydaje się biblioteka PHP GD. Używając getimagesize(), będziemy przetwarzać obraz za pomocą biblioteki GD. Jeśli nie jest to obraz, to się nie powiedzie i cały upload się nie powiedzie:

<?php
$verifyimg = getimagesize($_FILES['image']['tmp_name']);

if($verifyimg['mime'] != 'image/png') {
    echo "Only PNG images are allowed!";
    exit;
}

$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?>

Wciąż jeszcze nie jesteśmy. Większość typów plików graficznych umożliwia dodawanie do nich komentarzy tekstowych. Ponownie nic nie stoi na przeszkodzie, aby atakujący dodał jakiś kod php jako komentarz. Biblioteka GD oceni to jako doskonale poprawny obraz. Interpreter PHP całkowicie zignorowałby obraz i uruchom kod php w komentarzu. To prawda, że to zależy od konfiguracji php, które rozszerzenia plików są przetwarzane przez interpreter php, a które nie, ale ponieważ jest wielu programistów, którzy nie mają kontroli nad tą konfiguracją z powodu użycia VPS, nie możemy założyć, że interpreter php nie przetworzy obrazu. Dlatego dodanie rozszerzenia pliku biała lista nie jest wystarczająco bezpieczne albo.

Rozwiązaniem byłoby przechowywanie obrazów w miejscu, w którym atakujący nie ma bezpośredniego dostępu do pliku. Może to być poza głównym katalogiem dokumentu lub w katalogu chronionym przez a .plik htaccess:

order deny,allow
deny from all
allow from 127.0.0.1

Edit: po rozmowie z innymi programistami PHP, sugeruję użycie folderu poza głównym katalogiem dokumentu, ponieważ htaccess nie zawsze jest niezawodny.

Nadal potrzebujemy użytkownika lub innego odwiedzającego, aby móc wyświetlić obraz. Więc użyjemy php, aby pobrać dla nich obraz:

<?php
$uploaddir = 'uploads/';
$name = $_GET['name']; // Assuming the file name is in the URL for this example
readfile($uploaddir.$name);
?>

Drugi problem: lokalny ataki włączania plików
Chociaż nasz skrypt jest już w miarę bezpieczny, nie możemy założyć, że serwer nie cierpi z powodu innych luk. Wspólna luka w zabezpieczeniach jest znana jako Local file inclusion . Aby to wyjaśnić, muszę dodać przykładowy kod:

<?php
if(isset($_COOKIE['lang'])) {
   $lang = $_COOKIE['lang'];
} elseif (isset($_GET['lang'])) {
   $lang = $_GET['lang'];
} else {
   $lang = 'english';
}

include("language/$lang.php");
?>

W tym przykładzie mówimy o wielojęzycznej stronie internetowej. Język witryn nie jest czymś, co uważa się za informacje "wysokiego ryzyka". Staramy się, aby odwiedzający preferowali język poprzez plik cookie lub żądanie GET i dołącz wymagany plik na jego podstawie. Zastanów się, co się stanie, gdy atakujący wprowadzi następujący adres url:

Www.example.com/index.php?lang=../uploads/my_evil_image.jpg

PHP będzie zawierać plik przesłany przez atakującego, pomijając fakt, że nie może uzyskać dostępu do pliku bezpośrednio i wracamy do punktu wyjścia.

Rozwiązaniem tego problemu jest upewnienie się, że użytkownik nie zna nazwy pliku na serwerze. Zamiast tego będziemy zmiana nazwy pliku, a nawet rozszerzenia za pomocą bazy danych do śledzenia go:

CREATE TABLE `uploads` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(64) NOT NULL,
    `original_name` VARCHAR(64) NOT NULL,
    `mime_type` VARCHAR(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;


<?php

if(!empty($_POST['upload']) && !empty($_FILES['image']) && $_FILES['image']['error'] == 0)) {

    $uploaddir = 'uploads/';

    /* Generates random filename and extension */
    function tempnam_sfx($path, $suffix){
        do {
            $file = $path."/".mt_rand().$suffix;
            $fp = @fopen($file, 'x');
        }
        while(!$fp);

        fclose($fp);
        return $file;
    }

    /* Process image with GD library */
    $verifyimg = getimagesize($_FILES['image']['tmp_name']);

    /* Make sure the MIME type is an image */
    $pattern = "#^(image/)[^\s\n<]+$#i";

    if(!preg_match($pattern, $verifyimg['mime']){
        die("Only image files are allowed!");
    }

    /* Rename both the image and the extension */
    $uploadfile = tempnam_sfx($uploaddir, ".tmp");

    /* Upload the file to a secure directory with the new name and extension */
    if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {

        /* Setup a database connection with PDO */
        $dbhost = "localhost";
        $dbuser = "";
        $dbpass = "";
        $dbname = "";

        // Set DSN
        $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname;

        // Set options
        $options = array(
            PDO::ATTR_PERSISTENT    => true,
            PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
        );

        try {
            $db = new PDO($dsn, $dbuser, $dbpass, $options);
        }
        catch(PDOException $e){
            die("Error!: " . $e->getMessage());
        }

        /* Setup query */
        $query = 'INSERT INTO uploads (name, original_name, mime_type) VALUES (:name, :oriname, :mime)';

        /* Prepare query */
        $db->prepare($query);

        /* Bind parameters */
        $db->bindParam(':name', basename($uploadfile));
        $db->bindParam(':oriname', basename($_FILES['image']['name']));
        $db->bindParam(':mime', $_FILES['image']['type']);

        /* Execute query */
        try {
            $db->execute();
        }
        catch(PDOException $e){
            // Remove the uploaded file
            unlink($uploadfile);

            die("Error!: " . $e->getMessage());
        }
    } else {
        die("Image upload failed!");
    }
}
?>

Więc teraz zrobiliśmy co następuje:

  • stworzyliśmy bezpieczne miejsce do zapisywania obrazów
  • przetworzyliśmy obraz za pomocą biblioteki GD
  • sprawdziliśmy typ MIME obrazu
  • zmieniliśmy nazwę pliku i zmieniliśmy rozszerzenie
  • zapisaliśmy zarówno nową, jak i oryginalną nazwę pliku w naszej bazie danych
  • mamy również zapisywanie typu MIME w naszej bazie danych

Nadal musimy być w stanie wyświetlić obraz odwiedzającym. Po prostu używamy kolumny id w naszej bazie danych, aby to zrobić:

<?php

$uploaddir = 'uploads/';
$id = 1;

/* Setup a database connection with PDO */
$dbhost = "localhost";
$dbuser = "";
$dbpass = "";
$dbname = "";

// Set DSN
$dsn = 'mysql:host='.$dbhost.';dbname='.$dbname;

// Set options
$options = array(
    PDO::ATTR_PERSISTENT    => true,
    PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
);

try {
    $db = new PDO($dsn, $dbuser, $dbpass, $options);
}
catch(PDOException $e){
    die("Error!: " . $e->getMessage());
}

/* Setup query */
$query = 'SELECT name, original_name, mime_type FROM uploads WHERE id=:id';

/* Prepare query */
$db->prepare($query);

/* Bind parameters */
$db->bindParam(':id', $id);

/* Execute query */
try {
    $db->execute();
    $result = $db->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e){
    die("Error!: " . $e->getMessage());
}

/* Get the original filename */
$newfile = $result['original_name'];

/* Send headers and file to visitor */
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename='.basename($newfile));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($uploaddir.$result['name']));
header("Content-Type: " . $result['mime_type']);
readfile($uploaddir.$result['name']);
?>

Dzięki temu skryptowi odwiedzający będzie mógł obejrzeć obraz lub pobrać go z oryginalną nazwą pliku. Jednak nie może uzyskać dostępu do pliku na twoim serwerze bezpośrednio ani nie będzie w stanie oszukać twojego serwera, aby uzyskać dostęp do pliku dla niego/jej, ponieważ nie ma sposobu, aby wiedzieć, który plik to jest. (S) nie potrafi brutalnie Wymuś swój katalog upload, ponieważ po prostu nie pozwala nikomu uzyskać dostępu do katalogu, z wyjątkiem samego serwera.

I na tym kończy się mój bezpieczny skrypt wysyłania obrazów.

Chciałbym dodać, że nie uwzględniłem maksymalnego rozmiaru pliku w tym skrypcie, ale powinieneś być w stanie to zrobić sam.

Klasa ImageUpload
Ze względu na duże zapotrzebowanie na ten skrypt, napisałem klasę ImageUpload, która powinna ułatwić wszystkim bezpiecznie obsługuj obrazy przesłane przez odwiedzających witrynę. Klasa może obsługiwać zarówno pojedyncze, jak i wiele plików jednocześnie i zapewnia dodatkowe funkcje, takie jak wyświetlanie, pobieranie i usuwanie obrazów.

Ponieważ kod jest po prostu duży, aby opublikować tutaj, możesz pobrać klasę z MEGA tutaj: {]}

Pobierz ImageUpload Class

Po prostu przeczytaj README.txt i postępuj zgodnie z instrukcjami.

Going Open Source
Bezpieczny Obraz projekt klasowy jest teraz dostępny również na moim profilu Github . To tak, że inni (ty?) może przyczynić się do projektu i uczynić z niego wspaniałą bibliotekę dla wszystkich. (obecnie podsłuch. Proszę skorzystać z powyższego pobierania, dopóki nie zostanie naprawione).

 57
Author: icecub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 21:07:29

Cóż, przesyłanie plików w PHP jest zbyt łatwe i bezpieczne. Polecam lear o:

Aby przesłać plik w PHP masz dwie metody, PUT I POST(może więcej..). Aby użyć metody POST z potrzebą HTML enable enctype na formularzu takim jak:

<form action="" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="Upload">
</form>

Następnie w PHP musisz złapać przesyłasz plik z $_files Tak:

$_FILES['file']

Następnie musisz przenieść z temp ("upload") z move_uploaded_file:

if (move_uploaded_file($_FILES['file']['tmp_name'], YOU_PATH)) {
   // ...
}

A po załadowaniu pliku trzeba sprawdzić rozszerzenie i najlepszym i lepszym sposobem jest użycie pathinfo w ten sposób:

$extension = pathinfo($_FILES['file']['tmp_name'],PATHINFO_EXTENSION);

Ale rozszerzenie nie jest bezpieczne, ponieważ można przesłać plik z rozszerzeniem .jpg ale z mimetype text/php i to jest tylne drzwi. Więc polecam sprawdzić prawdziwy typ MIME z finfo_open w ten sposób:

$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['file']['tmp_name']);

I nie używaj $_FILES['file']['type'] ponieważ czasami i w zależności od przeglądarki i systemu operacyjnego klienta, można otrzymać application/octet-stream, a ten typ MIME nie jest prawdziwym typ MIME przesłanego pliku.

Myślę, że dzięki temu można przesłać plik z zabezpieczeniami.

Przepraszam mój angielski, Pa!

 2
Author: Olaf Erlandsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-29 12:12:00