PHP upload security

Iniciado por SH4V, 11 Diciembre 2009, 11:30 AM

0 Miembros y 1 Visitante están viendo este tema.

WHK

Cita de: temporal12345 en 21 Diciembre 2009, 05:07 AM
bueno el post, creo que solo faltaría quitar permisos de ejecución al directorio dónde se suben los archivos, y error_reporting(0).


Y si por esas casualidades de la vida tienes un sistema de uploads donde tus archivos se guardan en un directorio igual a tu nick o te da la posibilidad de renombrar archivos? que pasaría si alguien le pone de nombre de archivo "../../../../shell.php" ? ya no estaría en el directorio donde no tienes permisos de ejecución y no necesitas un error_reporting a cero para evitar la subida de la shell tal como pasaba con el mod de my_uploads de phpnuke, inluso a veces para hacerles bypass podías subir archivos php con el nombre de x.php. o x.php5 o x.php... y se ejecutaban igual xD o si no le subias un .htaccess para forzar una extensión .ext a ejecutable php

O_G_T

#11
hola que tal a todos  ;D
he pasado por este hilos que he encontrado googleando... he leido muchos temas publicados por sh4van3, algunos buenos otros no tantos debo suponer que se trata del mismo user SH4V de undersecurity.net  ya que todos los paper de sh4van3 son una copia exapta de SH4V....

Primero sh4van3 como tu dices Alla Bezroutchko publico esto  en el año 2007 y hoy es obsoleto... exiten muchas cosas que han cambiado desde esa fecha con relacion a php ....  hay muchas funciones antiguas y nuevas de php para el tratamiento de imagenes; como asi tambien existen muchos trucos que pocos revelan, yo no voy a revelar los mios, porque de esta manera mantendre seguras mis aplicaciones.  
Pero voy a mostrar algunas funciones que has descartado en tu codigo y que muchos dejan de lado a la hora de programar un upload de imagenes...

VAMOS AL HECHO:
exif_imagetype ---> determina el tipo de imagen
se puede utilizar en versiones PHP mayores a 4.3.0
ejemplo de uso

<?php
if (exif_imagetype('imagen_subida.gif') != IMAGETYPE_GIF) {
    echo 
'Hey! la imagen no es GIF!!! estas usando el tuto de sh4van3';
}else{ 
///codigo normal
}
?>



exif_read_data --> en versiones superiores 4.3.0 (recomendado)
lee los encabezados EXIF de un archivo de imagen JPEG o TIFF. De esta manera se puede obtenet los metadatos generados por cámaras digitales utilizadas.

testeo

<?php
echo "test1.jpg:<br />\n";
$exif exif_read_data('tests/test1.jpg''IFD0');
echo 
$exif===false "No header data found.<br />\n" "Image contains headers<br />\n";

$exif exif_read_data('tests/test2.jpg'0true);
echo 
"test2.jpg:<br />\n";
foreach (
$exif as $key => $section) {
    foreach (
$section as $name => $val) {
        echo 
"$key.$name$val<br />\n";
    }
}
?>



exif_thumbnail
<?php
if (array_key_exists('file'$_REQUEST)) {
    
$image exif_thumbnail($_REQUEST['file'], $width$height$type);
} else {
    
$image false;
}
if (
$image!==false) {
    
header('Content-type: ' .image_type_to_mime_type($type));
    echo 
$image;
    exit;
} else {
    
// no thumbnail available, handle the error here
    
echo 'No thumbnail available';
}
?>



LOS SOBRESALIENTES:
imagecopyresampled() copia una porción rectangular de una imagen sobre otra, suavizando los valores de los píxeles mediante interpolación, de forma que al reducir el tamaño de una imagen aún mantiene una buena claridad. img_dst  es la imagen de destino, img_org  es el identificador de la imagen de origen. Si las coordenadas de origen y destino y ancho y alto son diferentes, se encogerá o agrandará el fragmento de imagen según sea necesario. Las coordenadas son relativas a la esquina superior izquierda. Esta función se puede usar para copiar regiones dentro de la misma imagen (si img_dst  es la misma que img_org ) pero si las regiones se superponen, los resultados serán impredecibles.

imagecopyresized() copia una porción rectangular de una imagen hacia otra imagen. dst_im  es la imagen de destino, src_im  es el identificador de la imagen origen. Si la altura y anchura de las coordenadas de origen y destino difieren se realizará un estrechamiento o un estiramiento apropiado del fragmento de la imagen. Las coordenadas van localizadas sobre la esquina superior izquierda. Esta función se puede usar para copiar regiones dentro de la misma imagen (si dst_im  es igual que src_im ) pero si las regiones se solapan los resultados seran impredecibles.

   Nota: Existe un problema debido a las limitaciones de las imagenes con paleta de colores (255+1 colores). La acción de volver a muestrear o filtrar una imagen, normalmente requiere de más de 255 colores, con los que se usa una aproximación para calcular el nuevo pixel muestreado y su color.
   En las imagenes con paleta de color, se trata de obtener el nuevo color, o el color calculado más próximo en caso de error. Sin embargo, este nuevo color no siempre es el que visualmente es mas semejante. De esta forma, se pueden producir resultados extraños como imagenes vacías.
   Para solventar este problema, se recomienda usar imagenes de color real como imagen destino, que se pueden obtener por ejemplo mediante la función imagecreatetruecolor().

imagecreate Crea una nueva imagen con una paleta de colores

imagecreatefromgif Crear una nueva imagen a partir de un archivo o URL

imagecreatefromjpeg Crea una imagen nueva desde un archivo o URL
devuelve un identificador de imagen que representa a la imagen obtenida a partir del nombre de archivo indicado.
devuelve una cadena vacía si ha fallado. También escribe un mensaje de error, que desafortunadamente se muestra en el navegador como un enlace roto...

imagecreatefrompng() idem anteriores :-)

imagecreatetruecolor Crea una imagen nueva en color real (true color)
devuelve un identificador de imagen representando una imagen en blanco de tamaño anchura  por altura

image_type_to_mime_type envia Mime-Type para image-type utiles para getimagesize, exif_read_data, exif_thumbnail, exif_imagetype.

ahora veamos un ejemplo utilizando todo esto  :rolleyes:

EL SIGUIENTE CODIGO ES PARTE DE UNA APLICACION JQUERY, QUE CONSIDERO BUENA  ;D
function resizeImage($image,$width,$height,$scale) {
$image_data = getimagesize($image);
$imageType = image_type_to_mime_type($image_data[2]);
$newImageWidth = ceil($width * $scale);
$newImageHeight = ceil($height * $scale);
$newImage = imagecreatetruecolor($newImageWidth,$newImageHeight);
switch($imageType) {
case "image/gif":
$source=imagecreatefromgif($image);
break;
   case "image/pjpeg":
case "image/jpeg":
case "image/jpg":
$source=imagecreatefromjpeg($image);
break;
   case "image/png":
case "image/x-png":
$source=imagecreatefrompng($image);
break;
  }
imagecopyresampled($newImage,$source,0,0,0,0,$newImageWidth,$newImageHeight,$width,$height);

switch($imageType) {
case "image/gif":
  imagegif($newImage,$image);
break;
      case "image/pjpeg":
case "image/jpeg":
case "image/jpg":
  imagejpeg($newImage,$image,90);
break;
case "image/png":
case "image/x-png":
imagepng($newImage,$image);  
break;
   }

chmod($image, 0777);
return $image;
}
function resizeThumbnailImage($thumb_image_name, $image, $width, $height, $start_width, $start_height, $scale){
list($imagewidth, $imageheight, $imageType) = getimagesize($image);
$imageType = image_type_to_mime_type($imageType);

$newImageWidth = ceil($width * $scale);
$newImageHeight = ceil($height * $scale);
$newImage = imagecreatetruecolor($newImageWidth,$newImageHeight);
switch($imageType) {
case "image/gif":
$source=imagecreatefromgif($image);
break;
   case "image/pjpeg":
case "image/jpeg":
case "image/jpg":
$source=imagecreatefromjpeg($image);
break;
   case "image/png":
case "image/x-png":
$source=imagecreatefrompng($image);
break;
  }
imagecopyresampled($newImage,$source,0,0,$start_width,$start_height,$newImageWidth,$newImageHeight,$width,$height);
switch($imageType) {
case "image/gif":
  imagegif($newImage,$thumb_image_name);
break;
      case "image/pjpeg":
case "image/jpeg":
case "image/jpg":
  imagejpeg($newImage,$thumb_image_name,90);
break;
case "image/png":
case "image/x-png":
imagepng($newImage,$thumb_image_name);  
break;
   }
chmod($thumb_image_name, 0777);
return $thumb_image_name;
}
chmod($thumb_image_name, 0755);
return $thumb_image_name;
}

function getHeight($image) {
$size = getimagesize($image);
$height = $size[1];
return $height;
}

function getWidth($image) {
$size = getimagesize($image);
$width = $size[0];
return $width;
}


$large_image_location = $upload_path.$large_image_name;
$thumb_image_location = $upload_path.$thumb_image_name;


if(!is_dir($upload_dir)){
mkdir($upload_dir, 0755);
chmod($upload_dir, 0755);
}




CODIGO DEL ARCHIVO QUE PROCESARA LOS DATOS

<?php 

include'ARCHIVO-CON-EL-CODIGO-ANTERIOR.php'

if (
$_POST["upload"]=="Upload") { 
//RECIBE DATOS
$userfile_name $_FILES['image']['name'];
$userfile_tmp $_FILES['image']['tmp_name'];
$userfile_size $_FILES['image']['size'];
$userfile_type $_FILES['image']['type'];
$filename basename($_FILES['image']['name']);
$file_ext strtolower(substr($filenamestrrpos($filename'.') + 1));


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

foreach ($allowed_image_types as $mime_type => $ext) {

if($file_ext==$ext && $userfile_type==$mime_type){
$error "";
break;
}else{
$error "Only <strong>".$image_ext."</strong> images accepted for upload<br />";
}
}
//VERIFICA EL TAMAÑO
if ($userfile_size > ($max_file*1048576)) {
$error.= "Images must be under ".$max_file."MB in size";
}

}else{
$error"Please select an image for upload";
}
//SI TODO ESTA BIEN SUBE LA IMAGEN
if (strlen($error)==0){

if (isset($_FILES['image']['name'])){
/*/VERIFICA LA EXTENSION QUE ES TOMADA POR UNA SESION ESTA PARTE DE LA APLICACION NO HA SIDO PUBLICADA EN ESTE EJEMPLO ESTE ES MERO EJEMPLO DE EMPLEO DE LAS FUNCIONES CITADAS EN ESTE PAPER*/
$large_image_location $large_image_location.".".$file_ext;
$thumb_image_location $thumb_image_location.".".$file_ext;

//UNA VEZ CARGADO EL ARCHIVO SE AGREGA LA EXTENSION TOMANDO DESDE SESION 
if($_SESSION['user_file_ext']!=$file_ext){
$_SESSION['user_file_ext']="";
$_SESSION['user_file_ext']=".".$file_ext;
}

move_uploaded_file($userfile_tmp$large_image_location);
chmod($large_image_location0777);

$width getWidth($large_image_location);
$height getHeight($large_image_location);
//REDUCE LA IMAGEN SI ES MAYOR EN ANCHO QUE EL PERMITIDO
if ($width $max_width){
$scale $max_width/$width;
$uploaded resizeImage($large_image_location,$width,$height,$scale);
}else{
$scale 1;
$uploaded resizeImage($large_image_location,$width,$height,$scale);
?>



YO CREO QUE ES BUEN CODIGO, PODEIS PROBAR METIENDO TODO EL CODIGO QUE QUERAIS EN LA PELOTITA DEL EJEMPLO DE sh4van3 OS ASEGURO QUE PARA LO UNICO QUE SERVIRA LA PELOTITA ESA, ES  PARA METERSELA EN EL<? echo $mi_nick; ?>   ;D ;D ;D ;D ;D
SALUDOS!!!

WHK

Hola, creo que debiste haber puesto tu post en el foro de php y no acá porque lo único que veeo es como utilizar funciones descritas exactamente igual que en php.net

http://cl.php.net/exif
http://cl.php.net/image

Lo digo porque de seguridad no le veo mucho, incluso ese código está mal hecho porque en el caso que hagas multiupload el array de imagenes subidas va a pasar de ser dimensional a multidimensional y tu código va a fallar.
Para solucionar el problema de las transparencias y la cantidad de colores hay alternativas mucho mejores.
Por ejemplo este pequeño código que hize tomando la documentación desde php.net puede hacer lo mismo y mejor:

Código (php) [Seleccionar]
<?php
$original 
'test.jpg';
$destino 'test.png'// La salida siempre será en PNG para no perder calidad
 
$width_d 100// ancho de salida en pixeles
$height_d 100// alto de salida en pixeles
/* obtengo información del archivo */
list($width_s$height_s$type$attr) = getimagesize($original$info2);
/* crea el recurso gd para el origen */
if(!$gd_s imagecreatefromstring(file_get_contents($original)))
 die(
'El archivo no es una imagen.'); // el archivo no es una imagen
/* crea el recurso gd para la salida */
if(!$gd_d imagecreatetruecolor($width_d$height_d))
 die(
'El archivo no es una imagen.'); // No maneja GD o escala fuera del limite
 
magealphablending($gd_dfalse); // desactivo el procesamiento automatico de alpha
imagesavealpha($gd_dtrue); // Alpha original se graba en el archivo destino
/* Redimensiona */
imagecopyresampled($gd_d$gd_s0000$width_d$height_d$width_s$height_s);
unlink($original); // Elimina el archivo original
if(!imagepng($gd_d$destino)) // Graba la imagen
 
die('No tiene permisos de escritura sobre el directorio de imagenes.');
imagedestroy($gd_s); // Libera memoria
imagedestroy($gd_d); // Libera memoria
?>


Ahora, que alguien haya copiado el tuto de alguien mas lo dudo porque es lo mismo que puedes ver en php.net y por algo fueron hechas esas funciones y no solo el o tu lo utilizan sino miles de desarrolladores y no necesariamente lo vieron desde un tutorial.

Si quieres solucionar el problema de la subida de imagenes yo hize una función que puede solucionar eso, talves te sirva. En php.net también sale similar.

Código (php) [Seleccionar]
function guardar_subidos($directorio_almacenamiento, $extensiones_permitidas = false, $crear_directorio = false, $un_solo_archivo = false){

  /* Verifica si hay archivos subidos para ser recibidos */
  if(is_array($_FILES)){
   if(count($_FILES) < 1){
    return false;
   }
  }else{
   return false;
  }

  if($un_solo_archivo){
   if(count($_FILES) > 1){
    return array(
     'estado' => 'error',
     'descripcion_error' => 'Solo es permitido subir un solo archivo y se ha detectado mas de uno'
    );
   }
  }

  /* Verifica si el directorio de guardado es válido o no */
  if(!is_dir($directorio_almacenamiento)){
   if($crear_directorio){
    if(!mkdir($directorio_almacenamiento, 0755)){
     return array(
      'estado' => 'error',
      'descripcion_error' => 'El directorio a guardar los archivos subidos no existe y fue imposible crear'
     );
    }
   }else{
    return array(
     'estado' => 'error',
     'descripcion_error' => 'El directorio a guardar los archivos subidos no existe'
    );
   }
  }

  /* Limpia la ruta del directorio evitando doble slashses y null bytes */

  $directorio_almacenamiento = dirname($directorio_almacenamiento.'/archivo.ext').'/';

  /* Si no existe el directorio entonces lo creará */

  if(!is_dir($directorio_almacenamiento)){

   if(!mkdir($directorio_almacenamiento, 0755))

    return array(
     'estado' => 'error',
     'descripcion_error' => 'No se pudo crear el nuevo directorio. Verifique los permisos de escritura sobre el directorio raiz'
    );

  }


  /* Procesa cada archivo subido para pasar de un array con uno a dos dimensiones a una sola dimensión */
  foreach($_FILES as $nombre_array => $archivo){
   /* Verifica si se ha subido un solo archivo o varios */
   if(is_array($_FILES[$nombre_array]['name'])){ /* Múltiples archivos */
    /* Procesa cada archivo subido */
    foreach($_FILES[$nombre_array]['name'] as $id => $nombre){
     /* Verifica que no sea un input vacio */
     if($_FILES[$nombre_array]['name'][$id]){
      $subidos[] = array(
       'name' => $_FILES[$nombre_array]['name'][$id],
       'type' => $_FILES[$nombre_array]['type'][$id],
       'tmp_name' => $_FILES[$nombre_array]['tmp_name'][$id],
       'error' => $_FILES[$nombre_array]['error'][$id],
       'size' => $_FILES[$nombre_array]['size'][$id]
      );
     }
    }
   }else{ /* Un solo archivo */
    $subidos[] = $archivo;
   }
  }

  /* Verifica si se ha subido algún archivo */
  if(is_array($subidos)){
   /* Procesa cada archivo subido previamente filtrado en un solo array de una dimensión */
   foreach($subidos as $subido){
    if(is_array($extensiones_permitidas)){
     if(archivos::coincide_extension($subido['name'], $extensiones_permitidas)){
      if(move_uploaded_file($subido['tmp_name'], $directorio_almacenamiento.$subido['name'])){
       $resultados[] = array(
        'estado' => 'ok',
        'name' => $subido['name'],
        'type' => $subido['type'],
        'error' => $subido['error'],
        'size' => $subido['size']
       );
      }else{
       $resultados[] = array(
        'estado' => 'error',
        'descripcion_error' => 'El archivo "'.$subido['name'].'" no pudo ser movido. Verifique los permisos de escritura sobre el directorio raiz',
        'name' => $subido['name'],
        'type' => $subido['type'],
        'error' => $subido['error'],
        'size' => $subido['size']
       );
      }
     }else{
      $resultados[] = array(
       'estado' => 'error',
       'descripcion_error' => 'El archivo "'.$subido['name'].'" contiene una extensión no válida',
       'name' => $subido['name'],
       'type' => $subido['type'],
       'error' => $subido['error'],
       'size' => $subido['size']
      );
     }
    }else{
     if(move_uploaded_file($subido['tmp_name'], $directorio_almacenamiento.$subido['name'])){
      $resultados[] = array(
       'estado' => 'ok',
       'name' => $subido['name'],
       'type' => $subido['type'],
       'error' => $subido['error'],
       'size' => $subido['size']
      );
     }else{
      $resultados[] = array(
       'estado' => 'error',
       'descripcion_error' => 'El archivo "'.$subido['name'].'" no pudo ser movido',
       'name' => $subido['name'],
       'type' => $subido['type'],
       'error' => $subido['error'],
       'size' => $subido['size']
      );
     }
    }
   }
  }else{
   return array(
    'estado' => 'error',
    'descripcion_error' => 'No hay archivos para ser subidos'
   );
  }

  /* Retorna el resultado */
  return array(
   'estado' => 'ok',
   'datos' => $resultados
  );

}


Lo usas así:
Código (php) [Seleccionar]
$data = guardar_subidos('/archivos/imagenes/', array('doc','xls','pdf','ppt'), true); print_r($data);

En ves de hacer criticas destructivas podrias aportar algo que realmente valga la pena, si quieres criticarle algo a alguien mandale un privado mostrando tu molestia pero no vengas al foro lanzando piedras porque alguien le copió a otro que copió funciones desde php.net.