Seguridad en PHP al subir un archivo

Si en tu aplicación web desarrollada en php permites subir archivos al servidor, te puedes encontrar con algún problemilla de seguridad. A continuación describiré algunos agujeros de seguridad que se producen al subir un archivo y como implementar una solución segura.

Implementación básica

upload1.php

PHP:
  1. <?php
  2. $uploaddir = 'uploads/'; // Directorio relativo al raiz web
  3. $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
  4. if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  5. echo "Fichero subido correctamente.\n";
  6. } else {
  7. echo "Error al subir el archivo.\n";
  8. }
  9. ?>

Los usuarios podrán ver los archivos subidos navegando en http://www.midominio.com/uploads/filename.jpg

Y para subir los archivos les mostraremos un formulario como el siguiente:

PHP:
  1. <form name="upload" action="upload1.php" method="POST" ENCTYPE="multipart/formdata">
  2. Select the file to upload: <input type="file" name="userfile">
  3. <input type="submit" name="upload" value="upload">
  4. </form>

Un ataque no tiene porque usar el formulario anterior. Se puede escribir un script en Perl y subir archivos o usar un proxy para interceptar y modificar los datos subidos. El código anterior sufre un de un agujero mayor de seguridad. Permite a los usuarios subir ficheros arbitrariamente al directorio uploads/ a partir del raiz. Por lo tanto, se podria subir un fichero PHP y ejecutar comandos en el servidor. El siguiente script en PHP permite al usuario ejecutar comdandos de shell en el servidor.

PHP:
  1. <?php
  2. system($_GET['command']);
  3. ?>

Si éste fichero se encuentra en nuestro servidor, cualquiera podría ejecutar comandos de shell navegando por ésta dirección http://midominio.com/shell.php?command=Unix_shell_command.

Puedes encontrar en internet muchos más scripts de PHP Shells más sofisticados que permiten incluso ejecutar sentencias SQL.

Por lo tanto debemos controlar que tipo de ficheros se pueden subir al servidor
Comprobar el Content-type

upload2.php

PHP:
  1. <?php
  2. if($_FILES['userfile']['type'] != "image/gif") {
  3. echo "Sorry, we only allow uploading GIF images";
  4. }
  5. $uploaddir = 'uploads/';
  6. $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
  7. if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  8. echo "File is valid, and was successfully uploaded.\n";
  9. } else {
  10. echo "File uploading failed.\n";
  11. }
  12. ?>

En éste caso, si un usuario intenta subir shell.php, se comprobará el MIME type en la petición de subida del archivo y se rechazará.

Hemos solucionado un problema, pero la comprobación del MIME type se puede saltar fácilmente, como muestra el siguiente script en PERL.

PERL:
  1. #!/usr/bin/perl
  2. #
  3. use LWP;
  4. use HTTP::Request::Common;
  5. $ua = $ua = LWP::UserAgent->new;;
  6. $res = $ua->request(POST 'http://localhost/upload2.php',
  7. Content_Type => 'form-data',
  8. Content => [
  9. userfile => ["shell.php", "shell.php", "Content-Type" =>
  10. "image/gif"],
  11. ],
  12. );
  13. print $res->as_string();

El script en Perl anterior cambia el valor de la cabecera Content-type a image/gif, lo que permite que upload2.php acepte la subida del fichero, aunque sigue trantandose de un PHP shell script.

Validar en contenido del fichero imagen

Utilizaremos la función de PHP getimagesize(). Captura el nombre del fichero como un argumento y devuleve el tamaño y tipo de la imagen.

upload3.php

PHP:
  1. <?php
  2. $imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
  3. if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
  4. echo "Sorry, we only accept GIF and JPEG images\n";
  5. }
  6. $uploaddir = 'uploads/';
  7. $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
  8. if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  9. echo "File is valid, and was successfully uploaded.\n";
  10. } else {
  11. echo "File uploading failed.\n";
  12. }
  13. ?>

Leave a Comment