Hacker usó la carga de imágenes para obtener el código PHP en mi sitio

115

Estoy trabajando en un sitio web. En este momento se encuentra en las primeras etapas de las pruebas, aún no se ha lanzado y solo tiene datos de prueba. Gracias a Dios.

En primer lugar, un pirata informático descubrió la contraseña para iniciar sesión en las páginas de "administración" de los sitios web * . Creo que utilizaron un registrador de claves en la computadora de un amigo que inició sesión en el sitio para enviarme comentarios.

En segundo lugar, utilizaron un cuadro de carga de imágenes para cargar un archivo PHP. He puesto un estricto control para que solo se acepten los archivos .jpg y .png; todo lo demás debería haber sido rechazado. Seguramente no hay manera de cargar un archivo .jpg y luego cambiar la extensión una vez que se almacena el archivo.

Afortunadamente, también genero nuevos nombres de archivos cuando me envían un archivo, así que no creo que hayan podido localizar el archivo para ejecutar el código.

Parece que no puedo entender cómo el sitio web deja pasar un archivo PHP. ¿Qué pasa con mi seguridad? El código de la función de validación está abajo:

function ValidateChange_Logo(theForm)
{
   var regexp;
   if (theForm.FileUpload1.value == "")
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   var extension = theForm.FileUpload1.value.substr(theForm.FileUpload1.value.lastIndexOf('.'));
   if ((extension.toLowerCase() != ".jpg") &&
       (extension.toLowerCase() != ".png"))
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   return true;
}

Una vez que el archivo llega al servidor, uso el siguiente código para conservar la extensión y generar un nuevo nombre aleatorio. Es un poco desordenado, pero funciona bien.

// Process and Retain File Extension
$fileExt = $_FILES[logo][name];
$reversed = strrev($fileExt);
$extension0 = substr($reversed, 0, 1);
$extension1 = substr($reversed, 1, 1);
$extension2 = substr($reversed, 2, 1);
$fileExtension = ".".$extension2.$extension1.$extension0;
$newName = rand(1000000, 9999999) . $fileExtension;

Acabo de realizar pruebas con un nombre como logo.php;.jpg y, aunque el sitio web no puede abrir la imagen, cambió el nombre correctamente a 123456.jpg . En cuanto a logo.php/.jpg , Windows no permite tal nombre de archivo.

* Páginas protegidas en el sitio web que permiten funciones simples: como cargar una imagen que luego se convierte en un nuevo logotipo para el sitio web. Los detalles de FTP son completamente diferentes a la contraseña utilizada para iniciar sesión en las páginas protegidas en el sitio web. Como son las bases de datos y las credenciales de cPanel. Me he asegurado de que las personas ni siquiera puedan ver la estructura de carpetas y archivos del sitio. Literalmente, no se me ocurre ninguna forma de cambiar el nombre de una extensión .jpg o .png a .php en este sitio si no tiene detalles de FTP.

    
pregunta Williamz902 04.01.2017 - 11:04
fuente

11 respuestas

315

Validación del lado del cliente

El código de validación que ha proporcionado está en JavaScript. Eso sugiere que es el código que usa para hacer la validación en el cliente.

La regla número uno de asegurar webapps es nunca confiar en el cliente . El cliente está bajo el control total del usuario, o en este caso, el atacante. No puede estar seguro de que cualquier código que envíe al cliente se use para nada, y que ningún bloque que ponga en el cliente tenga ningún valor de seguridad. La validación en el cliente es solo para proporcionar una experiencia de usuario fluida, no para imponer realmente ninguna restricción relevante para la seguridad.

Un atacante podría simplemente cambiar esa pieza de JavaScript en su navegador, o desactivar los scripts completamente, o simplemente no enviar el archivo desde un navegador, sino crear su propia solicitud POST con una herramienta como curl.

Debe revalidar todo en el servidor. Eso significa que su PHP debe verificar que los archivos sean del tipo correcto, algo que su código actual no hace.

La forma de hacerlo es un problema general que creo que está fuera del alcance de su pregunta, pero esta pregunta son buenos lugares para comenzar a leer. Es posible que desee echar un vistazo a su .htaccess de archivos también.

Obteniendo la extensión

Tal vez no sea un problema de seguridad, pero esto es una mejor manera de hacerlo:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

Funciones de PHP mágicas

  

Cuando almaceno datos de cuadros de edición, utilizo las tres funciones de PHP para limpiarlos:

$cleanedName = strip_tags($_POST[name]); // Remove HTML tags
$cleanedName = htmlspecialchars($cleanedName); // Allow special chars, but store them safely. 
$cleanedName = mysqli_real_escape_string($connectionName, $cleanedName);

Esta no es una buena estrategia. El hecho de que mencione esto en el contexto de la validación de las extensiones de archivo hace que me preocupe que esté lanzando un par de funciones relacionadas con la seguridad en su entrada con la esperanza de que resuelva el problema.

Por ejemplo, debería usar declaraciones preparadas y no escapar para protegerse contra SQLi. El escape de etiquetas HTML y caracteres especiales para evitar XSS debe realizarse después de que los datos se recuperan de la base de datos, y cómo hacerlo depende de dónde inserte esos datos. Y así sucesivamente, y así sucesivamente.

Conclusión

No estoy diciendo que esto sea malo, pero parece que estás cometiendo muchos errores aquí. No me sorprendería si hubiera otras vulnerabilidades. Si su aplicación maneja información confidencial, le recomendaría encarecidamente que deje que alguien con experiencia en seguridad la examine antes de ponerla en producción.

    
respondido por el Anders 04.01.2017 - 11:12
fuente
36

En primer lugar, si alguien explotó una función de carga de archivos, asegúrese de que está verificando el archivo en el cliente y servidor . Omitir la protección de JavaScript del lado del cliente es trivial.

Segundo, asegúrese de revisar e implementar estas ideas de OWASP .

Eso debería cubrir la mayoría de tus problemas. Además, asegúrese de que no tiene ningún otro defecto no parcheado en su servidor (por ejemplo, complementos desactualizados, servidores explotables, etc.)

    
respondido por el thel3l 04.01.2017 - 11:12
fuente
26

Es súper importante tener en cuenta que PHP fue diseñado para integrarse en otro contenido . Específicamente, se diseñó para integrarse en páginas HTML, complementando una página en gran parte estática con fragmentos menores de datos actualizados dinámicamente.

Y, sencillamente, no le importa en qué está incrustado . El intérprete de PHP escaneará cualquier tipo de archivo arbitrario y ejecutará cualquier contenido de PHP que esté debidamente marcado con etiquetas de script.

El problema histórico que esto ha causado es que, sí, las cargas de imágenes pueden contener PHP. Y si el servidor web está dispuesto a filtrar esos archivos a través del intérprete de PHP, ese código se ejecutará. Ver el fiasco del plugin "Tim Thumb" de Wordpress.

La solución adecuada es asegurarse de que su servidor web nunca, nunca, trate el contenido no confiable como algo que debería ejecutar a través de PHP. Intentar filtrar la entrada es una forma de hacerlo, pero como ha encontrado, limitado y propenso a errores.

Es mucho mejor verificar que tanto la ubicación del archivo como la extensión del archivo se utilicen para definir qué procesará PHP y para segregar las cargas y otros contenidos no confiables en ubicaciones y extensiones que no se procesarán. (La forma en que lo hace varía de un servidor a otro y se ve afectada por el hecho de que "hacer que las cosas funcionen normalmente significa aflojar la seguridad de una forma que no conoce".)

    
respondido por el gowenfawr 04.01.2017 - 18:44
fuente
22

Puedes deshabilitar PHP en tu directorio de carga mediante .htaccess para que tu servidor no ejecute ningún código PHP en el directorio y en sus subdirectorios.

Cree un archivo .htaccess dentro de su directorio de carga con esta regla:

php_flag engine off

Tenga en cuenta que la regla .htaccess solo funcionará si su servidor PHP se está ejecutando en modo mod_php .

Editar: Por supuesto, olvidé mencionar que .htaccess solo funciona en Apache.

    
respondido por el medskill 05.01.2017 - 19:20
fuente
11

Solo está verificando la extensión, esto no necesariamente se correlaciona con el tipo de archivo real. En el lado del servidor, puede usar algo como exif_imagetype para verificar que el archivo es una imagen. uso de tipo de imagen exif: enlace

    
respondido por el iainpb 04.01.2017 - 14:19
fuente
6

Estoy totalmente de acuerdo con la respuesta aceptada, pero sugeriría hacer un poco más que simplemente jugar con el nombre del archivo. Debe volver a comprimir el archivo original / cargado con PHP usando GD o Imagick y usar la nueva imagen . De esta manera, destruye cualquier código inyectado (para ser honesto, el 90% del tiempo, hay formas de hacer que el código sobreviva a la compresión, pero es mucho trabajo).

También, podría usar archivos .htaccess para evitar que el directorio de carga ejecute el código PHP (no sé sobre IIS y probablemente haya un .webconfig equivalente).

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

De esta manera, el archivo cargado se ejecutará solo si la "extensión final" es PHP, por lo que no se ejecutará image.php.jpg.

Algunos recursos:

respondido por el akman 06.01.2017 - 13:24
fuente
6

Una posible parte de una solución aquí es mantener la imagen, pero también hacer una copia redimensionada con PHP de modo que pueda asegurarse de que está obteniendo una imagen y reducir el tamaño de la imagen original. Esto tiene varios efectos positivos:

  • Nunca permite que la imagen original se use para la visualización, por lo que es más segura
  • Ahorra espacio en su servidor (lo que puede o no ser un problema)
  • Mejor tiempo de carga para los visitantes del sitio, ya que no estarían esperando la carga de una imagen de 10 MB. En su lugar, tienen una imagen de 300 KB en un tamaño razonable cargando

El punto principal es que nunca se puede confiar en los datos de usuario. Valide todo, carga de archivos, entradas, cookies para asegurarse de que los datos no puedan causar problemas.

    
respondido por el LostBoy 04.01.2017 - 22:14
fuente
2

Solo tengo una pequeña cosa que agregar a esto que no se ha tocado: usar una estrategia de lista blanca.

Como otros ya lo han señalado, confiar en la validación del lado del cliente ciertamente no es algo bueno. También ha empleado una estrategia de lista negra, lo que significa que está revisando las cosas que sabe / piensa que son malas y está permitiendo todo lo demás. Una estrategia mucho más sólida es verificar si las cosas que usted sabe están bien y rechazar todo lo demás.

En ocasiones, esto es una compensación contra los comentarios de los usuarios, como permitir que los usuarios sin experiencia sepan lo que deben hacer para subir una imagen con éxito.

En mi experiencia, las dos estrategias no son necesariamente mutuamente excluyentes siempre que se usen para diferentes propósitos: validación de lista negra del lado del cliente para comentarios de los usuarios y validación de lista blanca del lado del servidor para la seguridad.

    
respondido por el Daniel 06.01.2017 - 11:00
fuente
2

Permito la carga de imágenes, pero asumo que cada una está cargada con código troyano. Coloco temporalmente la imagen cargada sobre la raíz web al cargarla, luego uso ImageMagick para convertirla a BMP, luego a JPG, luego la muevo a donde la aplicación web puede usarla. Esto elimina efectivamente cualquier código PHP incrustado.

Como @Sneftel señala en el comentario a continuación, realizar esta conversión en un JPG dará como resultado una degradación de la imagen. Para mi propósito, esta es una compensación aceptable.

    
respondido por el a coder 08.01.2017 - 14:04
fuente
1

Verifique las etiquetas EXIF de una imagen o use una función strpos para encontrar un código PHP en el contenido de la imagen. Ejemplo:

$imageFile = file_get_contents($your_image_temp_path); // ATTENTION! Temporary path for images is really needed for security reasons!
$dangerousSyntax = ['<?', '<?php', '?>'];
$error = '';
foreach($dangerousSyntax as $value) {
  $find = strpos($value, $imageFile);
  if( $find !== true ) {
    unlink($your_image_temp_path);
    $error = 'We found a dangerous code in your image';
  }
}

if(!empty($error)) { 
  die($error);
}
    
respondido por el Tomek Leszczynski 07.01.2017 - 20:46
fuente
0

Además de la respuesta aceptada, sugeriría usar la función getImageSize incorporada en PHP para obtenga el tipo MIME y asegúrese de que un archivo PHP no se oculte bajo una extensión de archivo de imagen.

    
respondido por el Grey 06.01.2017 - 09:37
fuente

Lea otras preguntas en las etiquetas