Me pidieron que continuara la serie de artículos sobre el cierre de vulnerabilidades y sobre cómo se cierran (nuestro primer artículo se puede leer
aquí ). La última vez descubrimos que incluso si el fabricante informa sobre el cierre de la vulnerabilidad, entonces en realidad todo puede no ser así.

Criterios de selección:
Los criterios de selección para las vulnerabilidades consideradas esta vez fueron los mismos (excepto que esta vez quería ver otros tipos de vulnerabilidades):
- debería haber un exploit: queremos ver que antes de la actualización todo estaba mal
explotado , y después de eso se volvió bueno; - la vulnerabilidad debería ser crítica (idealmente RCE) y con una puntuación alta;
- el producto debe ser de código abierto;
- el producto no debe ser abandonado y usado activamente;
- la vulnerabilidad debería ser relativamente nueva;
- como siempre, lo principal es que nosotros mismos estaríamos interesados.
Qué y cómo elegí:
Fui a
vulners.com y pedí que mostrara todos los exploits con
exploit-db.com en las últimas semanas. Esta vez en la categoría web, Ihsan Sencan creó casi todos los exploits, pero debido al hecho de que con mayor frecuencia se relacionan con inyecciones sql en aplicaciones y complementos antiguos no compatibles, los eliminé. De los productos restantes, solo la herramienta de gestión de proyectos ProjeQtOr 7.2.5 con vulnerabilidad CVE-2018-18924 cayó en la categoría de "no abandonada y en desarrollo activo".
Esta vulnerabilidad cumplió con todos los criterios de selección:
- hay una hazaña ;
- Vulnerabilidad RCE (aunque requiere que el usuario esté autorizado);
- el producto es de código abierto;
- el producto no se abandonó, en 2018 hubo 28 lanzamientos y solo sourceforge.net hubo 702 descargas (y la mayoría de las descargas de actualizaciones resuelven el problema de CVE, lo que probablemente indica que las personas vieron CVE y comenzaron a actualizar);
- CVE del 4 de noviembre, una hazaña del 25 de octubre, esto satisface los requisitos de novedad;
- Miré el problema y su solución, me interesé (más sobre eso más adelante).
Comprensión de la herramienta de gestión de proyectos ProjeQtOr
Leemos la descripción del
exploit y la descripción de
CVE en nist.gov , entendemos que la versión 7.2.5 solo es vulnerable para un usuario autorizado. Y también que puede cargar un archivo .shtml como imagen, aunque se mostrará el mensaje de error
"Este archivo no es una imagen válida" , pero el archivo se guardará en las imágenes del servidor y se podrá acceder a él mediante un enlace directo en
host / files / images / image_name .

El acceso a través de un enlace directo es bueno, pero aquí todavía tiene que adivinar el nombre con el que se descargará el archivo. Tenemos suerte, y no es aleatorio, sino que se genera a partir de la hora actual en el formato: año, mes, día, horas, minutos, segundos. El resultado es ese número 20181114140320. A continuación, la ID de usuario y luego el nombre del archivo original pasan por el guión bajo. Hay bastantes incógnitas:
- Zona horaria en el servidor
- si el reloj del servidor está inactivo;
- ID de usuario
Y nuevamente tenemos suerte: si carga una imagen válida, todos estos parámetros nos serán informados. No es difícil repasar varias opciones de enlaces (varios, ya que hay segundos, y es difícil acceder a ellos de inmediato).

En general, obtener el nombre del archivo no es un problema. Seguimos adelante. Y pensamos, ¿por qué no simplemente subir un script php? Estamos intentando descargarlo, aparece la misma ventana, pero el archivo no aparece en el directorio. ¡Es hora de mirar el código!
El script uploadImage.php es responsable de cargar la imagen al servidor en la versión 7.2.5. Estamos interesados en las líneas 100 a 117.
if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm') { if(@!getimagesize($uploadedFile['tmp_name'])) { $error=i18n('errorNotAnImage'); } else { traceHack("Try to upload php file as image in CKEditor"); } } else { if ( ! move_uploaded_file($uploadedFile['tmp_name'], $uploadfile)) { $error = htmlGetErrorMessage(i18n('errorUploadFile','hacking ?')); errorLog(i18n('errorUploadFile','hacking ?')); } } } if (!$error) { if(@!getimagesize($uploadfile)) { $error=i18n('errorNotAnImage'); } }
La línea 100 es responsable de verificar la extensión del archivo: si es php o phtm, el archivo se descarta y no se guarda. Por lo tanto, los archivos php no aparecen en el directorio "files / images /". La línea 115 crea el error que vemos, pero no hace nada con el archivo.
Bueno, no dejemos el exploit y carguemos el archivo con la extensión .shtml. Aquí vale la pena hacer una pequeña digresión y decir qué es .shtml y con qué se come.
SHTML y SSI
Definición de Wikipedia:
SSI (Server Side incluye - inclusión en el lado del servidor) - un lenguaje simple para el "ensamblaje" dinámico de páginas web en el servidor desde los componentes individuales y la entrega del documento HTML recibido al cliente. Implementado en el servidor web Apache usando el módulo mod_include. La función incluida en la configuración predeterminada del servidor web le permite incluir archivos HTML, por lo tanto, para usar las instrucciones, el archivo debe terminar con la extensión .shtml, .stm o .shtm.
En tus propias palabras:
SHTML es HTML que puede ejecutar conjuntos de instrucciones del lado del servidor. De lo útil, hay una función ejecutiva que ejecuta comandos arbitrarios en el servidor (sí, podemos descargar el archivo usando código HTML y ejecutarlo).
Aquí hay un código de muestra para ejecutar código arbitrario:
<!--
La buena noticia es que esta funcionalidad no está habilitada de forma predeterminada en el servidor Apache2, y para habilitarla debes bailar con una pandereta. En un par de horas de elegir la configuración, pude hacer que el retorno de las variables de entorno funcionara, pero no el comando. Aquí está mi código SSI:
<html> <head> <title>thegeekstuff.com</title> </head> <body> <p> Today is <!--
Si alguien te dice qué escribir en la configuración para que funcione correctamente, me encantaría leerlo.
Explotar vulnerabilidad
Si las estrellas convergen, puede descargar el archivo shtml y ejecutar comandos arbitrarios (o, como yo, ver la hora en el servidor).
Viendo el parche
La próxima versión es 7.2.6, pero no hay cambios con respecto a la vulnerabilidad que nos interesa (nist.gov nuevamente engañado).
Observamos la versión 7.2.7 y parece que todo está arreglado (los propios desarrolladores dicen que todo está arreglado solo en esta versión). Hay dos cambios clave:
1. Entre las extensiones prohibidas, se agregó "shtm" (si los primeros 4 caracteres son tales, shtml también se incluye aquí):
if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm' or substr($ext,0,4)=='shtm') {
2. Los archivos que no son imágenes ahora se eliminan:
if(@!getimagesize($uploadfile)) { $error=i18n('errorNotAnImage'); kill($uploadfile); }
Parece que puede divergir, porque las imágenes que no se borran, y shtml ni siquiera está tratando de persistir. Pero no siempre me gustó que trataran de resolver un problema con las listas negras. Por ejemplo, en algunos países, las compañías prohíben las redes sociales. Esto lleva al hecho de que los usuarios comienzan a usar los "espejos" de las redes sociales, donde se roban sus nombres de usuario y contraseñas. Sus contraseñas coinciden con las contraseñas corporativas, pero puede haber problemas mucho mayores que un empleado hojeando un instagram con una taza de café.
En la programación web y su seguridad, las listas negras también son malas.
Omitir la lista negra de ProjeQtOr
Bueno, todo es simple. Primero, veamos qué archivos puede interpretar Apache2 + PHP en la configuración predeterminada (todo se instaló en ubuntu 16.04 con un repositorio actualizado). La directiva "FilesMatch" es responsable de la capacidad de interpretar archivos. Hacemos una búsqueda en él con el comando "grep -r" <FilesMatch "/ etc / apache2" y aquí está el resultado:
/etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.ph(p[3457]?|t|tml)$"> /etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.phps$"> /etc/apache2/mods-available/php7.0.conf:<FilesMatch "^\.ph(p[3457]?|t|tml|ps)$"> /etc/apache2/sites-available/default-ssl.conf: <FilesMatch "\.(cgi|shtml|phtml|php)$"> /etc/apache2/apache2.conf:<FilesMatch "^\.ht">
En la configuración default-ssl.conf, todas las extensiones simplemente se enumeran en su totalidad; estas son: cgi, shtml, phtml, php. Por desgracia, todo excepto cgi se filtra en ProjeQtOr.
La configuración php7.0.conf es mucho más interesante, en ella las extensiones son establecidas por la expresión regular. Obtenemos:
Extensión | Lo que se filtra |
---|
php | substr ($ ext, 0.3) == 'php' |
php3 | substr ($ ext, 0.3) == 'php' |
php4 | substr ($ ext, 0.3) == 'php' |
php5 | substr ($ ext, 0.3) == 'php' |
php7 | substr ($ ext, 0.3) == 'php' |
pht | Nada |
phtml3 | substr ($ ext, 0.4) == 'phtm' |
Genial, se encuentra una extensión de archivo que no se filtra. Verificamos que realmente se interprete.
Cree un archivo test.pht con los siguientes contenidos:
<?php phpinfo();
Vamos a este archivo en el navegador y vemos información sobre el php instalado. Sorprendentemente, se omitió la lista negra, mientras que para configuraciones no predeterminadas, por alguna razón, se podrían permitir otras extensiones para la interpretación.
Cargamos nuestro archivo de prueba en la herramienta de gestión de proyectos ProjeQtOr. Por supuesto, recibimos un error, porque esto no es una imagen (en la versión anterior a 7.2.7, ya tenemos ejecución de código en el servidor, porque cambiar phpinfo para ejecutar comandos no es difícil). En la versión 7.2.7, el archivo se elimina y el código no se ejecuta.
Pero no estamos molestos y pasamos por alto el cheque de la imagen.
Imagen PHP
La función getimagesize, que simplemente mira el encabezado del archivo transferido, verifica si el archivo descargado es una imagen en la herramienta de gestión de proyectos ProjeQtOr.
Aprovechando el hecho de que puede haber basura en el archivo php, y la interpretación del código php comienza solo con los caracteres "<? Php", escribimos un pequeño código que primero escribe la imagen y luego el código php que necesitamos. Como imagen, enviaremos una captura de pantalla de la ventana de error. 3 líneas de código python y listo:
data = open ('test.png','rb').read() data += open ('test.pht','rb').read() open ('new_pht_png.pht','wb').write(data)
Probablemente, podría escribir un encabezado de imagen válido al comienzo del archivo, pero es más fácil, y aún más, la imagen se muestra en cualquier visor.
Subimos esta creación al servidor y, he aquí, se carga (y se muestra en el visor como una imagen), y también es bueno que se nos muestre el nombre completo con el que se descargó este archivo.

Vamos al archivo descargado
localhost / files / images / 20181114171730_1_new_pht_png.pht y vemos la imagen descargada como texto, y la salida de phpinfo debajo de ella. Está claro que reemplazar phpinfo con un simple shell web no es difícil. Por ejemplo, esto: <? Sistema Php ($ _ GET ['cmd']);

Una vez que comenzó a elegir descargas de archivos, debe finalizar el trabajo y ver dónde más hay una descarga de archivos con o sin listas negras.
Otro archivo cargado
Lo veremos en la última versión disponible. Suponiendo que use la misma función para cargar archivos que antes, es decir move_uploaded_file, lo buscamos en el directorio del proyecto "grep -r" move_uploaded_file "./". Obtenemos los siguientes 5 archivos:
./tool/uploadImage.php
./tool/saveDocumentVersion.php
./tool/uploadPlugin.php
./tool/import.php
./tool/saveAttachment.php
Archivo uploadImage.php - ya buscado.
Archivo saveDocumentVersion.php: descarga versiones de documentos (como su nombre lo indica). Estamos tratando de descargar un documento y mirarlo (para empezar, siempre cargaremos una imagen). Después de la descarga, vemos que la extensión .1 se agrega al archivo. Observamos en el código cómo se obtiene el nombre (esto se hace en la línea 229):
$uploadfile = $dv->getUploadFileName();
La función getUploadFileName se declara en el archivo DocumentVersionMain.php. Allí, en la línea 227, vemos que "." Se agrega al nombre devuelto. e identificación del documento. No podemos evitar incluso el punto agregado:
return $uploaddir . $paramPathSeparator . $fileName . '.' . $this->id;
El archivo uploadPlugin.php es accesible solo para los administradores y el hecho de que el complemento pueda tener un código incorrecto es muy lógico y difícil de eliminar sin tener que ingresar la validación del complemento (como hace el CMS popular). Por supuesto, cuando intentas descargar algo allí, se carga con éxito y luego se ejecuta.
El archivo import.php también está disponible solo para administradores. Al descargar un archivo, se nos dice que debe ser un archivo csv o un archivo xlsx. Por supuesto, tratamos de cargar el archivo php y vemos un error:
ERROR: el tipo de archivo proporcionado y el formato de archivo seleccionado no coinciden
Importación cancelada
El problema es que, como en el error original de CVE, el archivo no se elimina, pero permanece disponible en
localhost / files / attach / import / test.php .
El archivo saveAttachment se usa al cargar cualquier archivo adjunto (por ejemplo, al cargar su propia imagen). El script PHP no se arrastra allí, ya que hay una protección de la forma:
if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm' or substr($ext,0,4)=='shtm') { $attachment→fileName.=".projeqtor";
resulta que a los archivos de extensión php *, phtm *, shtm * se agrega la extensión ".projeqtor", es decir, obviamente nuestro archivo pht se arrastrará allí (incluso sin rastrear las imágenes). Lo intentamos y obtenemos todo en la dirección
localhost / files / attach / attach_1 / test.pht .
Resultado total de cinco ubicaciones de descarga de archivos encontradas rápidamente:
- logró cargar php o pht script 4 veces;
- la validación de la lista negra es en dos;
- la validación de la lista blanca no está en ningún lado;
- una vez no se pudo descargar el archivo (no se pudo descargar, pero no se pudo ejecutar), porque la expansión estaba cambiando
Conclusiones sobre la herramienta de gestión de proyectos ProjeQtOr y CVE-2018-18924
- la vulnerabilidad reportada ha sido virtualmente eliminada;
- hay otras vulnerabilidades en el código (informadas a los desarrolladores, e incluso prometieron listas blancas de extensiones);
- la configuración adecuada del servidor Apache2 podría salvarnos de todo (limite los formatos ejecutables solo a lo necesario, prohíba la ejecución de scripts en las carpetas de usuario);
- nist.gov no contiene la última versión vulnerable.
Nota de la señora
- rechazar listas negras siempre que sea posible (no sé dónde es imposible);
- tenga cuidado y esté atento al procesar archivos descargados (es mejor que esté en un solo lugar y no se extienda por 5);
- un servidor web configurado correctamente evita muchos problemas en el código del proyecto (es importante escribir código y configurar bien el servidor).
Respuesta detallada de desarrolladores
La primera respuesta de los desarrolladores fue algo así como "arreglamos todo, así que vea el código corregido". Tuve que pintar con gran detalle dónde están algunos problemas y cómo se pueden explotar.
Luego recibió una respuesta detallada: “sí, hay problemas y se solucionarán en la versión 7.3.0. También se agregarán listas blancas para imágenes para xlslx y csv ". También escribieron que tienen una recomendación para agregar los directorios de "archivos adjuntos" y "documentos" fuera del acceso web en las instrucciones de instalación.
Los desarrolladores me permitieron registrar CVE y escribir un artículo después de la actualización (que salió y está disponible para
descargar ).
Conclusión
Como escribí al principio, muchas personas han descargado la actualización decidiendo CVE (más de 500 descargas), y es genial que las personas actualicen su software vulnerable, pero es triste que el software siga siendo vulnerable.
Como resultado, se nos asignaron cuatro CVE a mí y a nuestra empresa: CVE-2018-19307, CVE-2018-19308, CVE-2018-19309, CVE-2018-19310.