¿Cómo resolver un fallo al validar tipos mime para carga de imágenes en Zendframework 3x? – php zend

Pregunta:


Estoy desarrollando un módulo de extensión para Zendframework 3x (cuyos detalles no son importantes para el tema.) Este debe contar con un utilitario para carga de imágenes desde el cliente, de modo que las validaciones son muy importantes, particularmente por los ataques por suplantación de tipos y por incrustación de código malicioso en ciertos tipos de imágenes como .gif que hacen posible incluir bloques ejecutables dentro de comentarios).

Para resolver el asunto extendí la clase IsImage de ZF3 que ofrece un conjunto de validadores que parecen adecuados y prometen ahorrar mucho trabajo. Grosso modo:

class ImageMimeGeter extends IsImage {
    protected $tipoencontrado = '';

    public function isValid($value, $file = null) {
        $done = parent::isValid($value, $file);
        $this->tipoencontrado = ($done) ? $this->type : '';
        return $done;
    }

    public function tipoMime(){
        return $this->tipoencontrado;
    }
    // sigue código no pertinente para la consulta
}

Al construir instancias de esta clase para validar los tipos de los archivos de imagen que subo al servidor de desarrollo el proceso aborta y devuelve la típica página en blanco de ZF3 que revela un fallo grave. (Literalmente el “sistema se cae”).

Estuve haciendo validaciones revisando paso a paso los métodos que ejecutan la clase madre que extendí y las que aquella extiende y logré identificar el momento (llamado) que aborta el proceso de validación:

/* recojo la información de archivo en $_FILES y la formateo 
   correctamente para poderla pasar a los validadores 
   el método recogerImagenACargar() no existe lo supongoo para
   ilustrar la situación. (el mecanismo de formateo es complejo porque
   $_FILES es un array con varios formatos posibles que dependen de que 
   la carga sea individual o múltiple y de si hay uno o varios elementos
   input de tipo file) */
$imagen_a_subir = recogerImagenACargar($_FILES);
$validador = new ImageMimeGeter();
$esvalida = $validador->isValid($imagen_a_subir);
/* En este punto me podrían decir que no estoy respetando la signatura 
   del método isValid() pero no es cierto, acepta que le sea pasado el
   array de información del archivo a subir como único parámetro */

// El método de validación delega en parent la tarea, invocando:
$done = parent::isValid($value, $file);

/* Entré a revisar el código de ZF3 y me encontré con que el proceso 
   avanza sin fallos hasta el momento marcado abajo (copio parcialmente
   los métodos de ZF3 involucrados ya que cabe esperar que quien brinde 
   ayuda conoce el framework) */
/**
 * NOTA: Este método se encuentra en la clase MimeType
 *
 * Defined by ZendValidatorValidatorInterface
 *
 * Returns true if the mimetype of the file matches the given ones. Also parts
 * of mimetypes can be checked. If you give for example "image" all image
 * mime types will be accepted like "image/gif", "image/jpeg" and so on.
 *
 * @param  string|array $value Real file to check for mimetype
 * @param  array        $file  File data from ZendFileTransferTransfer (optional)
 * @return bool
 */
public function isValid($value, $file = null)
{
    if (is_string($value) && is_array($file)) {
        // Legacy ZendTransfer API support
        $filename = $file['name'];
        $filetype = $file['type'];
        $file     = $file['tmp_name'];
    } elseif (is_array($value)) {
        if (!isset($value['tmp_name']) || !isset($value['name']) || !isset($value['type'])) {
            throw new ExceptionInvalidArgumentException(
                'Value array must be in $_FILES format'
            );
        }
        $file     = $value['tmp_name'];
        $filename = $value['name'];
        $filetype = $value['type'];
    } else {
        $file     = $value;
        $filename = basename($file);
        $filetype = null;
    }
    $this->setValue($filename);

    // Is file readable ?
    if (empty($file) || false === stream_resolve_include_path($file)) {
        $this->error(static::NOT_READABLE);
        return false;
    }

    // AQUÍ Aborta el proceso. Falla al ejecutar getMagicFile()
    $mimefile = $this->getMagicFile();
    // Dejo el resto del método porque ilustra un excelente tratamiento
    // de la validación de tipos mime (Razón por la que me gusta ZFx)
    if (class_exists('finfo', false)) {
        if (!$this->isMagicFileDisabled() && (!empty($mimefile) && empty($this->finfo))) {
            ErrorHandler::start(E_NOTICE|E_WARNING);
            $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $mimefile);
            ErrorHandler::stop();
        }

        if (empty($this->finfo)) {
            ErrorHandler::start(E_NOTICE|E_WARNING);
            $this->finfo = finfo_open(FILEINFO_MIME_TYPE);
            ErrorHandler::stop();
        }

        $this->type = null;
        if (!empty($this->finfo)) {
            $this->type = finfo_file($this->finfo, $file);
        }
    }

    if (empty($this->type) && $this->getHeaderCheck()) {
        $this->type = $filetype;
    }

    if (empty($this->type)) {
        $this->error(static::NOT_DETECTED);
        return false;
    }

    $mimetype = $this->getMimeType(true);
    if (in_array($this->type, $mimetype)) {
        return true;
    }

    $types = explode('/', $this->type);
    $types = array_merge($types, explode('-', $this->type));
    $types = array_merge($types, explode(';', $this->type));
    foreach ($mimetype as $mime) {
        if (in_array($mime, $types)) {
            return true;
        }
    }

    $this->error(static::FALSE_TYPE);
    return false;
}

Luego de identificar que el proceso avanza sin problemas hasta el punto señalado arriba getMagicFile() entré a revisar dicho procedimiento paso a paso e identifiqué el posible germen del mal:

/**
 * Returns the actual set magicfile
 *
 * @return string
 */
public function getMagicFile()
{
    if (null === $this->options['magicFile']) {
        $magic = getenv('magic');
        if (!empty($magic)) {
            // Si hubiese pasado por acá se habría caído como ocurre más
            // abajo
            $this->setMagicFile($magic);
            if ($this->options['magicFile'] === null) {
                $this->options['magicFile'] = false;
            }
            return $this->options['magicFile'];
        }

        foreach ($this->magicFiles as $file) {
            try {
            // En este punto se cae a pesar del try que se presume
            // atrapa el error y permite seguir
                $this->setMagicFile($file);
            } catch (ExceptionExceptionInterface $e) {
                // suppressing errors which are thrown due to open_basedir restrictions
                continue;
            }

            if ($this->options['magicFile'] !== null) {
                return $this->options['magicFile'];
            }
        }

        if ($this->options['magicFile'] === null) {
            $this->options['magicFile'] = false;
        }
    }

    return $this->options['magicFile'];
}

Antes de tratar de revisar qué pasa con el método setMagicFile($file), revisé el array de archivos mágicos recogidos por getenv() y sus paths para descartar problemas de privilegios de acceso. obtuve la siguiente lista:

    /usr/share/misc/magic
    /usr/share/misc/magic.mime
    /usr/share/misc/magic.mgc
    /usr/share/mime/magic
    /usr/share/mime/magic.mime
    /usr/share/mime/magic.mgc
    /usr/share/file/magic
    /usr/share/file/magic.mime
    /usr/share/file/magic.mgc

que como se puede observar están en directorios compartidos a los que el aplicativo tiene acceso.
Pasé, entonces a revisar el método setMagicFile($file) y me encontré que camina hasta invocar un método intrínseco de php: finfo_open()

Pase a consultar la documentación oficial de php (manual en español) y encontré un warning desagradable (que motiva esta farragosa consulta):

Advertencia
El formato esperado de la base de datos mágica cambió en PHP 5.3.11 y 5.4.1. Debido a esto, la base de datos mágica interna ha sido actualizada. Esto mayormente efectua código donde se use una base de datos mágica externa: leer un fichero mágico antiguo ahora fallará. Además, algunas representaciones textuales de los tipos mime han cambiado, por ejemplo, para PHP sería devuelto “PHP script, ASCII text” en lugar de “PHP script text”.

El servidor que empleo para desarrollo tiene instalado Php 5.6 y el sistema es Ubuntu 18.04 que a su vez tiene instalado Php 7.2 De modo que me lleva a pensar que muy seguramente los fallos detectados provienen del uso del método finfo_open().

Hice una prueba aparte de funcionamiento del método en cuestión pasando uno a uno los elementos del array mime (anotado arriba) para tratar de provocar el fallo y me encontré que no derriba el sistema en ningún caso, aunque falla en la mayoría, usé el siguiente código:

<?php
    $mimefiles = [
        '/usr/share/misc/magic',
        '/usr/share/misc/magic.mime',
        '/usr/share/misc/magic.mgc',
        '/usr/share/mime/magic',
        '/usr/share/mime/magic.mime',
        '/usr/share/mime/magic.mgc',
        '/usr/share/file/magic',
        '/usr/share/file/magic.mime',
        '/usr/share/file/magic.mgc'
    ];
    foreach ($mimefiles as $mf) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE, $mf);
        if($finfo){
            mdf('Abierto '.$mf, 'warning');
        }else{
            mdf('FALLO al Abrir '.$mf, 'warning');
        }
        finfo_close($finfo);
    }
// mdf() es una función de mi API personal que me ofrece mensajes de
// depuración con formato (similar a un Dump pero con formato amigable)

y obtuve la siguiente salida:

Abierto /usr/share/misc/magic
FALLO al Abrir /usr/share/misc/magic.mime
FALLO al Abrir /usr/share/misc/magic.mgc
FALLO al Abrir /usr/share/mime/magic
FALLO al Abrir /usr/share/mime/magic.mime
FALLO al Abrir /usr/share/mime/magic.mgc
Abierto /usr/share/file/magic
FALLO al Abrir /usr/share/file/magic.mime
FALLO al Abrir /usr/share/file/magic.mgc

De modo que la hipótesis del fallo del método es falsa. En consecuencia, me encuentro en una encrucijada que me obliga a solicitar sugerencias. Si usted soportó todo el cuento, le estoy muy agradecido, si además puede ofrecerme una sugerencia razonable, tendré con usted una deuda inmensa. Gracias por “escucharme” leer.

Preguntado por: quevedo

Fuente

Related Posts:

array push con keys php – php array
Pregunta: Buen día. Estoy tratando de crear un array con valores agrupados por categorías, mediante AJAX recibo dos variables una contiene un string con el nombre ...
Actualizar información con ajax php mysql – php jquery mysql
Pregunta: Tengo esta simple consulta que lo que simplemente me muestra las visitas de la web, pero quisiera que fuera automático con ajax ya que ...
¿Cómo insertar código en las entradas de WordPress? – php wordpress plugin
Pregunta: En algunos blogs de programación he visto algo como esto en las entradas: Me gustaría poder hacer lo mismo. Sin embargo, no solo quiero que ...
Como validar array checkbox – javascript php jquery
Pregunta: Que tal buen dia, alguien me podria decir como se puede hacer para que cuando presione el boton "BAJAR DATOS" si este esta vacio ...
Problema con UTF_8 – php sql sql-server
Pregunta: Buen día, mi problema es el siguiente, Php me lanza el siguiente Warning Warning: utf8_decode() expects parameter 1 to be string, object given ...
Obtener las fechas de los días Lunes dependiendo de un rango de fechas PHP – php datetime time
Pregunta: Dependiendo de un rango de fechas quiero obtener sólo las fechas de los días Lunes, tengo la siguiente función que funciona pero me trae ...
Customizar rutas de Login en Laravel 5.3 – php laravel laravel-5
Pregunta: Hola tengo un problema con el enrutado de Login con el metodo php artisan make:auth que te genera todo lo que se necesita Desde vamos ...
¿Cómo utilizar `count` para contabilizar la cantidad de registros para distintos valores de un campo en mysql? – php mysql sql
Pregunta: Resulta que tengo un campo llamado ESTADO en mi tabla. En este campo hay varias estados de pedido que son: LOGÍSTICA, PACKING, PICKING, CARTERA, ...
Precargar SOAP en PHP – php webservice soap
Pregunta: Estoy usando un webservice en el cual realizo la conexión por SOAP en PHP. El problema viene cuando en la primera llamada, a veces, no ...
Xampp no carga proyecto laravel – php laravel apache
Pregunta: Tengo el proyecto laravel en un mac con OSX el capitán con php artisan. El proyecto se ejecuta sin problema, pero por motivos personales ...
URLs absolutas con PHP – php pretty-urls
Pregunta: Explico mi problema, es sencillo, lo tengo solucionado, pero solamente de manera local, no me sirve lo que hice si yo uso esto en ...
Ejecutar función de otro controlador enviando parametros – php laravel laravel-5
Pregunta: Tengo a Controller1 el cual contiene una función llamada actualizarCartera($parametro1, $parametro2, $parametro3), desde Controller2 ¿Cómo hago para ejecutar esta función?, ¿Cómo debo incluír a ...
PHPExcel comentario en negro – php excel
Pregunta: Llevamos un tiempo usando PHPexcel para rellenar unas tablas desde la Base de Datos, lleva funcionando meses pero últimamente al escribir un comentario y ...
Update con MYSQL PHP – php mysql mysqli
Pregunta: Tengo este código, pero no hace el update, y no sé porque. <?php // if ($_POST == "" || $_POST == "" || $_POST == ""){ ...
alguien sabe porque no imprime el json – php json
Pregunta: $from $to = 20; $query = "SELECT idempleados,nombre,apellidos FROM empleados LIMIT ?,?"; $result = $mysqli->prepare($query); $result->bind_ param('ii', $from, $to); $result->execute(); /* bind result variables */ $result->bind_result($idempleados,$nombre,$apellidos); /* fetch values */ while ($result->fetch()){ ...
Tags:,

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *