<?php
/**
* Subida de ficheros al servidor PHP 8
*
* Autor: David Carrero https://programacion.net
* Original: programacion.net (2003)
*
* Versión actualizada con move_uploaded_file(), validación de MIME real,
* lista blanca de extensiones y nombre aleatorio para el fichero destino.
* El original usaba copy() sobre $HTTP_GET_VARS, inseguro y eliminado en PHP 7.
*/
// Configuración
define('UPLOAD_DIR', __DIR__ . '/uploads/');
define('MAX_BYTES', 5 * 1024 * 1024); // 5 MB
const EXTENSIONES = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'zip'];
const MIMES = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf', 'text/plain', 'application/zip',
];
//
$mensaje = '';
$estado = ''; // 'ok' | 'error'
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['fichero'])) {
$f = $_FILES['fichero'];
try {
// 1. Errores internos de PHP (tamaño en php.ini, subida incompleta )
if ($f['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException(match ($f['error']) {
UPLOAD_ERR_INI_SIZE,
UPLOAD_ERR_FORM_SIZE => 'El fichero supera el tamaño máximo permitido.',
UPLOAD_ERR_PARTIAL => 'La subida se interrumpió antes de completarse.',
UPLOAD_ERR_NO_FILE => 'No se seleccionó ningún fichero.',
default => "Error en la subida (código {$f['error']}).",
});
}
// 2. Tamaño máximo propio (independiente de php.ini)
if ($f['size'] > MAX_BYTES) {
throw new RuntimeException('El fichero supera el límite de 5 MB.');
}
// 3. Extensión (lista blanca)
$ext = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION));
if (!in_array($ext, EXTENSIONES, true)) {
throw new RuntimeException("Extensión .{$ext} no permitida.");
}
// 4. Tipo MIME real se lee del fichero en disco, no del navegador
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);
if (!in_array($mime, MIMES, true)) {
throw new RuntimeException("Tipo de fichero no permitido ({$mime}).");
}
// 5. Crear directorio de destino si no existe
if (!is_dir(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0755, true);
}
// 6. Nombre aleatorio para evitar sobreescrituras y path traversal
$nombreFinal = bin2hex(random_bytes(8)) . '.' . $ext;
$destino = UPLOAD_DIR . $nombreFinal;
// 7. move_uploaded_file() verifica que el fichero proviene de una
// subida HTTP real (evita ataques de fichero arbitrario)
if (!move_uploaded_file($f['tmp_name'], $destino)) {
throw new RuntimeException('No se pudo mover el fichero al directorio destino.');
}
$mensaje = "Fichero guardado como <strong>{$nombreFinal}</strong>.";
$estado = 'ok';
} catch (RuntimeException $e) {
$mensaje = htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
$estado = 'error';
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Subir fichero al servidor</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 2rem auto;
padding: 0 1rem; background: #f5f5f5; color: #222; }
form { background: #fff; padding: 1.5rem; border: 1px solid #ddd;
border-radius: 6px; margin-bottom: 1.5rem; }
label { font-weight: bold; }
input[type="file"] { display: block; margin: .6rem 0 .3rem; }
input[type="submit"] { margin-top: .75rem; padding: .4rem 1.4rem; cursor: pointer; }
.msg { padding: .75rem 1rem; border-radius: 4px; margin-bottom: 1rem; }
.ok { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
.error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
small { color: #666; font-size: .875rem; }
code { background: #eee; padding: .1em .3em; border-radius: 3px; }
ul li { margin-bottom: .4rem; }
</style>
</head>
<body>
<h1>Subir fichero al servidor</h1>
<?php if ($mensaje !== ''): ?>
<div class="msg <?= $estado ?>"><?= $mensaje ?></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<label for="fichero">Selecciona un fichero:</label>
<input type="file" id="fichero" name="fichero">
<small>JPG, PNG, GIF, PDF, TXT, ZIP — máximo 5 MB</small>
<input type="submit" value="Subir fichero">
</form>
<h2>Cómo funciona</h2>
<ul>
<li><code>move_uploaded_file()</code> verifica que el fichero procede de una subida HTTP real, no de una ruta arbitraria del servidor.</li>
<li>El tipo MIME se comprueba con <code>finfo</code> sobre el contenido real del fichero, no sobre el nombre que envía el navegador (que se puede falsificar fácilmente).</li>
<li>El nombre del fichero se sustituye por un hash aleatorio con <code>random_bytes()</code> para evitar colisiones y ataques de path traversal.</li>
<li>Protege el directorio <code>uploads/</code> con un <code>.htaccess</code> que impida ejecutar scripts dentro de él (ver fichero adjunto en el ZIP).</li>
</ul>
</body>
</html>
Subir ficheros al servidor con PHP 8
Sube ficheros al servidor con PHP 8 de forma segura: move_uploaded_file() en lugar del inseguro copy() original, validación de MIME real con finfo, lista blanca de extensiones, límite de tamaño configurable y nombre aleatorio para evitar colisiones y path traversal. El ZIP incluye también un .htaccess para proteger el directorio de uploads contra la ejecución de scripts.
Descargar adjuntos
COMPARTE ESTE TUTORIAL
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP