Site icon Sophos News

Un fallo en Ghostscript podría permitir que documentos maliciosos ejecutaran comandos del sistema

Aunque no hayas oído hablar del proyecto Ghostscript, es muy posible que lo hayas utilizado sin saberlo.

También es posible que lo hayas incorporado a un servicio en la nube que ofreces, o que lo tengas preinstalado y listo para usar si utilizas un software basado en paquetes, como una distribución BSD o Linux, Homebrew en Mac o Chocolatey en Windows.

Ghostscript es una implementación gratuita y de código abierto del ampliamente utilizado sistema de composición de documentos PostScript de Adobe y de su aún más utilizado formato de archivo PDF, abreviatura de Portable Document Format (Formato de Documento Portátil). Internamente, los archivos PDF se basan en el código PostScript para definir cómo componer un documento.

Por ejemplo, el popular programa de gráficos de código abierto Inkscape utiliza Ghostscript entre bastidores para importar archivos de gráficos vectoriales EPS (Embedded PostScript), como los que puedes descargar de una biblioteca de imágenes o recibir de una empresa de diseño.

En pocas palabras, Ghostscript lee el código del programa PostScript (o EPS, o PDF), que describe cómo construir las páginas de un documento, y lo convierte, o lo renderiza (por usar la palabra técnica), en un formato más adecuado para su visualización o impresión, como datos de píxeles sin procesar o un archivo gráfico PNG.

Por desgracia, hasta la última versión de Ghostscript, ahora la 10.01.2, el producto tenía un error, denominado CVE-2023-36664, que podía permitir que documentos maliciosos no solo crearan páginas de texto y gráficos, sino que también enviaran comandos de sistema al motor de renderizado de Ghostscript y engañaran al software para que los ejecutara.

Pipes y pipelines

El problema surgió porque el manejo de Ghostscript de los nombres de archivo para la salida permitía enviar la salida a lo que en la jerga se conoce como un “pipe” en lugar de a un archivo normal.

Los “pipes”, como sabrás si alguna vez has programado o escrito scripts, son objetos del sistema que simulan ser archivos, en el sentido de que puedes escribir en ellos como en un disco, o leer datos de ellos, utilizando funciones normales del sistema como read() y write() en sistemas tipo Unix, o ReadFile() y WriteFile() en Windows, pero los datos no terminan realmente en el disco.

En lugar de eso, el extremo de “escritura” de un pipe simplemente transporta los datos de salida a un bloque temporal de memoria, y el extremo de “lectura”, lee cualquier dato que ya esté en la “pipeline” de memoria, como si procediera de un archivo permanente en el disco.

Esto es muy útil para enviar datos de un programa a otro.

Cuando quieras tomar la salida del programa UNO.EXE y utilizarla como entrada para DOS.EXE, no necesitas guardar primero la salida en un archivo temporal y luego volver a leerla utilizando los caracteres “>”  y “<” para redirigir el archivo, de esta forma:

C:\Users\duck> UNO.EXE > TEMP.DAT
C:\Users\duck> DOS.EXE < TEMP.DAT

Este método conlleva varios inconvenientes, entre los que se incluyen los siguientes:

Con un “pseudoarchivo” intermedio basado en memoria en forma de pipe, puedes condensar este tipo de cadena de procesos en:

 C:\Users\duck> UNO.EXE | DOS.EXE

Puedes ver en esta notación de dónde proceden los nombres de pipe y pipeline, y también por qué el símbolo de barra vertical (|) elegido para representar el conducto (tanto en Unix como en Windows) se conoce más comúnmente en el mundo de la informática como el carácter pipe.

Dado que los archivos-que-son-en-realidad-pipes-a-nivel-del-sistema-operativo se utilizan casi siempre para la comunicación entre dos procesos, ese carácter mágico del pipe suele ir seguido no de un nombre de archivo en el que escribir para su uso posterior, sino del nombre de un comando que consumirá la salida de inmediato.

En otras palabras, si permites que el contenido suministrado remotamente especifique un nombre de archivo que se utilizará para la salida, debes tener cuidado si permites que ese nombre de archivo tenga una forma especial que diga: “No escribas en un archivo; en su lugar, inicia un pipe, utilizando el nombre de archivo para especificar un comando que ejecutar”.

Cuando las funciones se convierten en errores

Al parecer, Ghostscript tenía una “función” de este tipo, por la que podías decir que querías enviar la salida a un nombre de archivo con un formato especial que empezara por %pipe% o simplemente |, dándote así la posibilidad de lanzar sigilosamente un comando de tu elección en el ordenador de la víctima.

No lo hemos probado, pero suponemos que también puedes añadir opciones de línea de comandos, así como un nombre de comando para ejecutar, lo que te da un control aún más preciso sobre qué tipo de comportamiento malicioso provocar en el otro extremo.

Curiosamente, si esa es la palabra correcta, el problema de “a veces los parches necesitan parches” volvió a surgir en el proceso de corrección de este error.

En el artículo de esta semana sobre un fallo en un plugin de WordPress, describimos cómo los creadores del plugin con el fallo (Ultimate Member) han pasado reciente y rápidamente por cuatro parches intentando solucionar un fallo de escalada de privilegios.

También hemos escrito recientemente sobre el software de intercambio de archivos MOVEit, que publicó tres parches en rápida sucesión para hacer frente a una vulnerabilidad de inyección de comandos que apareció por primera vez como un día cero en manos de delincuentes de ransomware.

En este caso, el equipo de Ghostscript añadió primero una comprobación como ésta, para detectar la presencia del peligroso texto %pipe… al principio de un nombre de archivo:

/* "%pipe%" no sigue las reglas normales de definición de rutas, por lo qu
   no los "reducimos" para evitar resultados inesperados */

if (len > 5 && memcmp(ruta, "%pipe", 5) != 0) {
   . . .

Entonces los programadores se dieron cuenta de que su propio código aceptaría tanto un carácter | plano como el prefijo %pipe%, por lo que el código se actualizó para tratar ambos casos.

Aquí, en lugar de comprobar que la ruta variable no empieza por %pipe… para detectar que el nombre de archivo es “seguro”, el código declara que el nombre de archivo no es seguro si empieza por un carácter de tubería (|) o por el temido texto %pipe…:

/* "%pipe%" no sigue las reglas normales de las definiciones de ruta, por lo que
   no las "reducimos" para evitar resultados inesperados */

if (ruta[0] == '|' || (len > 5 && memcmp(ruta, "%tubería", 5) == 0)) {
   . . .

Arriba, verás que si memcmp() devuelve cero, significa que la comparación fue VERDADERA, porque los dos bloques de memoria que estás comparando coinciden exactamente, aunque cero en los programas en C se utilice convencionalmente para representar FALSO. Esta molesta incoherencia surge del hecho de que memcmp() en realidad te indica el orden de los dos bloques de memoria. Si el primer bloque se ordenara alfanuméricamente antes que el segundo, te devuelve un número negativo, para que puedas saber que aardvark precede a zymurgy1. Si son al revés, obtienes un número positivo, que deja el cero para denotar que son idénticos. Así

#include <string.h>
#include <stdio.h>

int main(void) {
   printf("%d\n",memcmp("aardvark", "zymurgy1",8));
   printf("%d\n",memcmp("aardvark", "00NOTES1",8));
   printf("%d\n",memcmp("aardvark", "aardvark",8));
   return 0;
}
---output---
-1
1
0

¿Qué puedes hacer?

Exit mobile version