Upload un arxiu
Aquí teniu un exemple complet i segur d'un sistema d'upload de fitxers en PHP, acompanyat de comentaris que detallen les mesures de seguretat preses i la seva justificació.
Assumirem la següent estructura de fitxers al servidor:
-
/var/www/html/upload.php(L'script de càrrega) -
/var/www/html/formulari.html(El formulari) -
/var/www/fitxers_privats/uploads/(El directori privat de destí, fora de l'arrel públicahtml)
Formulari HTML (formulari.php)
Aquest formulari és el punt de partida.
HTML
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>Upload Segur amb PHP</title>
</head>
<body>
<h1>Pujar Arxiu</h1>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2097152" />
<label for="arxiu">Selecciona un fitxer (Màx. 2MB, només JPG, PNG, PDF):</label>
<input type="file" name="fitxer_pujat" id="arxiu" required>
<br><br>
<button type="submit" name="submit">Pujar Fitxer</button>
</form>
</body>
</html>
Script PHP (upload.php)
Aquest script implementa totes les validacions de seguretat necessàries abans de desar el fitxer.
PHP
<?php
// 1. Definició de Rutes i Paràmetres de Seguretat
// --------------------------------------------------
// DIRECTORI DE DESTÍ: MOLT IMPORTANT - Fora de l'arrel pública (/var/www/html)!
// Això prevé l'execució directa de codi maliciós.
$directori_desti = "/var/www/fitxers_privats/uploads/";
// LÍMIT DE MIDA (en bytes): 2 MB
// Aquesta validació de codi és la que REALMENT es fa servir.
$mida_maxima = 2097152;
// TIPUS DE FITXERS PERMESOS: Definició estricta de MIME types
$tipus_mime_permesos = [
'image/jpeg', // Per a arxius JPG
'image/png', // Per a arxius PNG
'application/pdf' // Per a arxius PDF
];
// Comprovem si s'ha enviat el formulari
if (!isset($_POST['submit'])) {
die("Accés denegat. Utilitza el formulari d'upload.");
}
// 2. Comprovació de l'Estat Inicial i Errors de PHP
// --------------------------------------------------
$fitxer = $_FILES['fitxer_pujat'];
if ($fitxer['error'] !== UPLOAD_ERR_OK) {
// Es comprova el codi d'error proporcionat per PHP
switch ($fitxer['error']) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
die("Error: El fitxer és massa gran. Mida màxima permesa: " .
($mida_maxima / 1024 / 1024) . " MB.");
case UPLOAD_ERR_NO_FILE:
die("Error: No s'ha seleccionat cap fitxer.");
default:
die("Error inesperat en la pujada. Codi: " . $fitxer['error']);
}
}
// 3. Validació de Mida i Tipus MIME
// ----------------------------------
// VALIDACIÓ DE MIDA: Comprovació estricta en bytes.
if ($fitxer['size'] > $mida_maxima) {
die("Error de seguretat: El fitxer excedeix el límit de " . ($mida_maxima / 1024 / 1024) . " MB.");
}
// VALIDACIÓ DE TIPUS MIME: Utilitzem la informació del fitxer temporal.
$tipus_real = mime_content_type($fitxer['tmp_name']);
if (!in_array($tipus_real, $tipus_mime_permesos)) {
die("Error de seguretat: Tipus de fitxer no permès ($tipus_real). Només acceptem JPG, PNG i PDF.");
}
// 4. Preparació de l'Arxiu de Destí (Renombratge Segur)
// ------------------------------------------------------
// OBTENCIÓ DE L'EXTENSIÓ SEGURA (Basada en el MIME type real)
$extensio_segura = match ($tipus_real) {
'image/jpeg' => '.jpg',
'image/png' => '.png',
'application/pdf' => '.pdf',
default => null, // No hauria de passar mai si la validació anterior és correcta
};
if (is_null($extensio_segura)) {
die("Error intern: Extensió no reconeguda.");
}
// GENERACIÓ DEL NOM SEGUR: Ús d'un ID únic i l'extensió segura.
// MAI es fa servir el nom original de l'usuari. Això prevé atacs de path traversal
// i fitxers amb doble extensió.
$nom_nou = uniqid('upload_', true) . $extensio_segura;
$desti_final = $directori_desti . $nom_nou;
// 5. Verificació Final i Moviment de l'Arxiu
// -------------------------------------------
// VERIFICACIÓ CRÍTICA: Comprovem que el fitxer realment ha estat pujat mitjançant HTTP POST
if (!is_uploaded_file($fitxer['tmp_name'])) {
die("Error de seguretat: La font de l'arxiu no és una pujada HTTP vàlida.");
}
// MOVIMENT: Desplacem l'arxiu de la ruta temporal a la ruta permanent.
if (move_uploaded_file($fitxer['tmp_name'], $desti_final)) {
// Configuració de Permisos del fitxer al servidor (opcional)
// Permet només lectura pel propietari i l'usuari/grup del servidor web.
// chmod($desti_final, 0644);
echo "L'arxiu s'ha pujat amb èxit! Nom de l'arxiu al servidor: **" . $nom_nou . "**";
// Nota: El fitxer es pot descarregar mitjançant un script intermediari (veure explicació prèvia).
} else {
die("Error: No s'ha pogut moure l'arxiu a la destinació final. Revisar permisos de la carpeta.");
}
?>
Mesures aplicades
| Mesura de Seguretat | Codi Implementat | Motiu |
| Ubicació Segura | $directori_desti = "/var/www/fitxers_privats/uploads/"; |
S'emmagatzema el fitxer fora de l'arrel pública del web. Això assegura que el servidor web no pugui executar el fitxer directament, obligant a utilitzar un script PHP controlat per servir-lo. |
| Comprovació d'Errors de PHP | if ($fitxer['error'] !== UPLOAD_ERR_OK) |
Gestiona errors de límit de mida (UPLOAD_ERR_INI_SIZE) o si no s'ha seleccionat cap fitxer, evitant que l'script continuï amb dades invàlides. |
| Validació de Mida Estricta | if ($fitxer['size'] > $mida_maxima) |
Evita l'esgotament de recursos del servidor (Denial of Service - DoS) i garanteix que la mida del fitxer s'ajusti a la lògica de negoci de l'aplicació. |
| Validació de Tipus MIME | mime_content_type($fitxer['tmp_name']) i in_array(...) |
És crucial per verificar la identitat real del fitxer (no només l'extensió del nom) i garantir que és un dels formats permesos (JPG, PNG, PDF), prevenint la càrrega de scripts executables. |
| Renombratge i Extensió Segura | uniqid() i $extensio_segura |
No confiar mai en el nom original. Es genera un nom únic i s'afegeix una extensió definida per l'aplicació. Això neutralitza atacs de path traversal (../../) i evita que un fitxer maliciós pugui ser executat amb una doble extensió (p. ex., script.php.jpg). |
| Verificació de Font | if (!is_uploaded_file(...)) |
Aquesta és una funció de seguretat crítica de PHP que assegura que el fitxer temporal prové realment d'una petició HTTP POST. Evita que un atacant mogui fitxers arbitraris d'altres parts del sistema de fitxers si pogués manipular la variable $_FILES. |