lunes, 26 de septiembre de 2011

Ver actualizaciones Ubuntu + CentOS + OpenSuSE

Justamente en la entrada anterior veíamos una de las posibles consecuencias de no actualizar el software que utilizamos.  Si bien es cierto que actualizar indiscriminadamente puede ser un tanto riesgoso en entornos de producción, (podríamos romper alguna dependencia y dejar inútil una aplicación) actualizar ES NECESARIO!!!

Actualizar puede ser una tarea simple y hasta divertida cuando se tratan de unos pocos servidores, pero ya no tanto cuando el número comienza a ascender! Por lo tanto la idea de esta entrada es definir un mecanismo que nos permita:

-Identificar entre actualizaciones de seguridad y el resto.
-Automatizar los avisos de actualizaciones.

El modelo es el siguiente:

-Utilizamos nagios para recolectar y presentar la información.
-No buscaremos actualizaciones cada 5 minutos, las actualizaciones no salen con tanta frecuencia por lo tanto estaríamos consumiendo ancho de banda de manera innecesaria. En el esquema planteado los servidores buscaran sus actualizaciones una vez en el día, y el resto del día mostraran los datos de esa recolección hasta que sean actualizados.
-Tendremos 2 scripts:
  1. updateNombreDelSO.pl que dependerá del sistema operativo y será el que se ejecute automáticamente una vez al día para recolectar las actualizaciones disponibles. Este script generará el segundo.
  2. check_actualizaciones.sh que será el script que ejecuté nrpe en los servidores, bajo la demanda del servidor corriendo nagios. Este script es la salida del primero, solo imprime la cantidad de actualizaciones pendientes de seguridad y extra, y un código de salida acorde a la situación.
-Definimos como CRITICAL la situación en la que existen actualizaciones de seguridad sin aplicar, y como WARNING cuando solamente existen actualizaciones extras (no de seguridad) por aplicar.

updateUbuntu.pl

#!/usr/bin/perl
$STATUS_OK=0;
$STATUS_WARNING=1;
$STATUS_CRITICAL=2;
$STATUS_UNKNOWN=3;
$PATCHS=`/usr/lib/update-notifier/apt-check 2>&1`;
@A=split(';',$PATCHS);
$SALIDA="#!/bin/bash\n";
$EXIT=$STATUS_UNKNOWN;
$FILE="/usr/lib/nagios/plugins/check_actualizaciones.sh";
if($A[0] eq "0" and $A[1] eq "0")
{
    open(F,">$FILE");
    print F "$SALIDA"."echo \"Existen ".$A[0]." actualizaciones.\"\n";
    print F "exit $STATUS_OK";
    close(F);
    chmod (0777,$FILE);
    exit;
}
if($A[0] > 0)
{
    $SALIDA = $SALIDA . "echo \"ERROR - Existen ".$A[0]." actualizaciones de seguridad y ".$A[1]." extras\"\n";
    $EXIT = $STATUS_CRITICAL;
}
else
{
    $SALIDA = $SALIDA . "echo \"WARNING - Existen ".$A[1]." actualizaciones extras.\"\n";
    $EXIT = $STATUS_WARNING;
}
open(F,">$FILE");
print F $SALIDA;
print F "exit $EXIT";
close(F);
chmod (0777,$FILE);

El script es bastante, sencillo,  "/usr/lib/update-notifier/apt-check" nos devuelve una linea de la forma act_seguridad;act_extras, que separamos con split y guardamos así el número de actualizaciones de seguridad en $A[0] y el número de actualizaciones extras en $A[1]. Luego de eso simplemente definimos la salida según las actualizaciones que haya para hacer. La salida de este script es un archivo bash con el formato:

#!/bin/bash
echo "ERROR -  Existen 3 actualizaciones de seguridad y 2 extras"
exit 2

 Este archivo es el que ejecutará nrpe cada vez que nagios consulte por el plugin, por lo tanto debemos agregarlo al archivo de configuracion de nrpe:

#echo "command[check_actualizaciones]=/usr/lib/nagios/plugins/check_actualizaciones.sh">> /etc/nagios/nrpe.cfg

Ahora nos encargamos de que ĺa recolección de actualizaciones se haga una vez al día:

#echo "@daily root /path/to/script/updateUbuntu.pl" >> /etc/crontab

No olvidarse de reiniciar nrpe una vez que se agregue la linea y se ejecute manuelmente por primera vez "/path/to/script/updateUbuntu.pl".

updateCentOS.pl

Para este script necesitamos instalar un agregado de yum, ejecutar como root:  "yum -y install yum-security" .

#!/usr/bin/perl

$STATUS_OK=0;
$STATUS_WARNING=1;
$STATUS_CRITICAL=2;
$STATUS_UNKNOWN=3;

$SALIDA="#!/bin/bash\n";
$EXIT=$STATUS_UNKNOWN;
$FILE="/usr/lib/nagios/plugins/check_actualizaciones.sh";
@PATCHS=`yum -q --security check-update 2>/dev/null`;

$A[0]=0;
$A[1]=0;
foreach $i (@PATCHS)
{
    $A[0]=$A[0]+1;  
}

@PATCHS=`yum -q check-update 2>/dev/null`;
foreach $i (@PATCHS)
{
    $A[1]=$A[1]+1;  
}
if($A[0] eq "0" and $A[1] eq "0")
{
        open(F,">$FILE");
        print F "$SALIDA"."echo \"Existen ".$A[0]." actualizaciones.\"\n";
        print F "exit $STATUS_OK";
        close(F);
        chmod (0777,$FILE);
        exit;
}
if($A[0] > 0)
{
       $SALIDA = $SALIDA . "echo \"ERROR - Existen ".$A[0]." actualizaciones se seguridad y ".$A[1]." extras\"\n";
       $EXIT = $STATUS_CRITICAL;
}
else
{
        $SALIDA = $SALIDA . "echo \"WARNING - Existen ".$A[1]." actualizaciones extras.\"\n";
        $EXIT = $STATUS_WARNING;
}
open(F,">$FILE");
print F $SALIDA;
print F "exit $EXIT";
close(F);
chmod (0777,$FILE);

Este script mantiene el mismo concepto, salvo que en lugar de ejecutar un solo programa ejecuta dos, el primero obtiene las actualizaciones de seguridad disponibles y el segundo las extras.

También debemos agregar la linea en el archivo de configuración de nrpe como se explicó antes, así como también la línea en crontab para la ejecución. Reiniciar nrpe y ejecutarlo manualmente la primera vez.

updateOpenSuSE.pl

#!/usr/bin/perl

$STATUS_OK=0;
$STATUS_WARNING=1;
$STATUS_CRITICAL=2;
$STATUS_UNKNOWN=3;

@PATCHS=`zypper -q lp 2>/dev/null | grep -i needed`;
$FLAG=0;
$c=0;

$SALIDA="#!/bin/bash\n";
$EXIT=$STATUS_UNKNOWN;
$FILE="/usr/lib/nagios/plugins/check_actualizaciones.sh";

foreach $i (@PATCHS)
{
    $c=$c+1;
    @aux=split('\|',$i);
    #solo uso los campos 1, 2 y 3
    if($i =~ /security/)
    {
        push(@SEGURIDAD,$aux[1]);
    }
    else
    {
        push(@OTROS,$aux[1]);
    }
    $FLAG=1;
}

if(@SEGURIDAD == 0 and @OTROS == 0)
{
        open(F,">$FILE");
        print F "$SALIDA"."echo \"Existen ".@SEGURIDAD." actualizaciones.\"\n";
        print F "exit $STATUS_OK";
        close(F);
        chmod (0777,$FILE);
        exit;
}

if(@SEGURIDAD > 0)
{
        $SALIDA = $SALIDA . "echo \"ERROR - Existen ".@SEGURIDAD." actualizaciones de seguridad y ".@OTROS." extras\"\n";
        $EXIT = $STATUS_CRITICAL;
}
else
{
        $SALIDA = $SALIDA . "echo \"WARNING - Existen ".@OTROS." actualizaciones extras.\"\n";
        $EXIT = $STATUS_WARNING;
}

open(F,">$FILE");
print F $SALIDA;
print F "exit $EXIT";
close(F);
chmod (0777,$FILE);


Nuevamente pero esta vez para openSuSE, también se deben aplicar los puntos de los otros dos casos.

No voy a incluir la configuración de nagios (por tiempo, ya me dio sueño :P, si alguien la precisa la tengo), pero básicamente consiste en definir un servicio y utilizar nrpe para ejecutar remotamente "check_actualizaciones.sh", de esta forma nagios nos estaría avisando cuántas actualizaciones pendientes tenemos y de qué tipo son.

Cabe aclarar que si actualizamos un servidor, las alertas no se irán hasta que no se vuelva a hacer la recolección, pero como esto pasa una vez al día estaríamos viendo alertas el resto del día. Por lo tanto recomiendo que una vez actualizado el servidor se ejecute manualmente el archivo de recolección para que las alarmas se vayan :D.

Concejos, dudas y demás serán bien recibidos!


lunes, 12 de septiembre de 2011

Prueba de concepto CVE-2011-3200 rsyslog

Es una tendencia y una muy buena práctica la centralización de los logs de los equipos. Existen varias opciones, siendo las mas difundidas rsyslog, syslog-ng y syslogd.
En esta entrada vamos a comprobar una vulnerabilidad (debilidad o falta de un control) que fue encontrada hace un par de semanas en el servidor de logs rsyslog.
La vulnerabilidad es básicamente un desbordamiento de pila, provocado por un excesivo tamaño del campo TAG. Este, es uno de los campos que compone un mensaje tipo syslog (RFC3164 http://www.ietf.org/rfc/rfc3164.txt).

El contexto de pruebas es el siguiente:

-Servidor rsyslog remoto en la dirección 192.168.206.160 (OpenSuSE 11.3)
-Cliente rsyslog en la dirección 192.168.206.1 (Ubuntu 9.10)

Software utilizado:

-rsyslog 5.4.0 en el servidor
-rsyslog 4.2.0 en el cliente
-logger en el cliente
-hping3 en el cliente

Configurar el servidor rsyslog:

Primeramente configuramos nuestro servidor rsyslog para que reciba mensajes remotos, esto se hace editando el archivos /etc/rsyslog.d/remote.conf y descomentando las siguientes líneas:

$ModLoad imudp.so
$UDPServerRun 514

Luego reiniciamos rsyslog, y abrimos el puerto 514 UDP en el firewall para poder recibir los mensajes.

Configurar el cliente rsyslog:

En este ejemplo vamos a indicar al rsyslog que corre en la pc cliente, que envíe al servidor remoto los logs que tengan facility local7 y severity warn (o mayor a warn); agregamos la siguiente linea a /etc/rsyslog.d/50-default.conf

local7.warn    @192.168.206.160

Probando la configuración con logger:

Logger es una interface de comandos que nos comunica con el sistema de loggin del equipo local. Es decir, logger NO enviará los mensajes al servidor remoto, sino al rsyslog local y este los enviará a donde correspondan.

logger -p local7.err -d -t Prueba "esto es una prueba"

Y en la red vemos:


Y del lado del servidor, con tail -f /var/log/messages veremos como llega nuestro mensaje:



Entonces ya tenemos andando nuestro servidor de logs centralizado (.... por decirlo de una manera :P).

Ahora veamos qué pasa si rompemos la barrera de los  32 caracteres en el campo TAG del mensaje, utilizando logger.

# logger -p local7.err -d -t PruebaPruebaPruebaPruebaPruebaTTX "esto es una prueba"

Captura de wireshark:


CARAMBA! Podemos apreciar que la X que excede el límite de los 32 caracteres nunca salió de nuestro sistema, es decir que nuestro rsyslog local cortó la cadena para solamente enviar lo permitido.

Bien, de momento podemos asegurar que un rsyslog no tiraría abajo otro rsyslog porque jamás enviaría un TAG que superase el límite que dicta el RFC.

Qué pasa si... creamos a mano un mensaje syslog y violamos esta restricción?

Para simular el mensaje copiamos de wireshark la parte de datos del paquete UDP y lo pegamos abriendo un archivo en modo binario (vi -b crafted_packet), de la siguiente forma:


Ahora el TAG tiene "PruebaPruebaPruebaPruebaPruebaTTX" que son 33 caracteres, es decir la misma prueba que hicimos con logger, pero esta vez será de manera manual, salteandonos el rsyslog local.

Pero cómo metemos este mensaje en un paquete UDP y lo enviamos al servidor???

hping3 es la respuesta a esa pregunta!!! hping3 es una aplicación esencialmente diseñada para hacer ataques de flooding xD, pero en este caso la usaremos con fines menos escabrosos. Entre otras cosas nos permite crear paquetes ICMP, IP, TCP, UDP de forma muy flexible y enviarlos a tasas dañinas o no.

Lo que haremos es generar paquetes UDP al puerto 514 del servidor de logs remoto en la IP 192.168.206.160.

La forma de generar el paquete es la siguiente:


-2 : indica que queremos generar paquetes UDP
-i 1: cantidad de paquetes por segundo
-d 78: tamaño del PDU UDP, es decir de los datos del paquete UDP
-E crafted_packet: es el archivo de 78 bytes que creamos anteriormente
-p 514: indica el puerto de destino de los paquetes
192.168.206.160: es la IP del servidor remoto

En la parte inferior de la imagen vemos los dos paquetes que recibió el servidor remoto, y se puede apreciar la X extra que excede los 32 caracteres. Por lo tanto queda claro que el buffer donde se guarda esta variable tiene mas de 32 bytes de espacio.

Bueno, ahora a reventarlo de una vez por todas.

Ahora generemos un ultra_crafted_packet (muajaja muajaja), básicamente es el crafted_packet, pero con 1024 X en lugar de una. Al archivo lo generé con un MUY feo script en perl.

#!/usr/bin/perl -w
open(F,">ultra_crafted_packet");
$inicio="<187>Sep 11 15:25:32 moon PruebaPruebaPruebaPruebaPruebaTT";
$fin=" esto es una prueba";
$mid="";
foreach $i (1 .. 1024)
{
    $mid="X".$mid;
}
print F $inicio.$mid.$fin;
close(F);

Ejecutamos el script y ya tenemos nuestro super ultra_crafted_packet, con un total de 1101 bytes. Entonces lanzamos hping3, cambiando los parámetros que corresponden (-d 1101 y -E ultra_crafted_packet).


Y en el servidor nos encontramos con una grata sorpresa :D :


La muerte súbita de rsyslogd y el backtrace correspondiente.

Entonces nos encontramos ante un sencillo caso de denegación al servicio de login centralizado. Solo basta con 1 paquete que exceda los límites del buffer donde se almacena el campo TAG del mensaje syslog, para que el servicio muera.

POR SUERTE la mayoría (sino todas) de las distribuciones ya han publicado el parche correspondiente. Por lo tanto actualizar el servidor es crucial.

#zypper lu -t patch | grep rsyslog


Nos muestra disponible el parche de seguridad 5099, que corresponde a esta vulnerabilidad. Lo aplicamos y volvemos a probar.

#zypper in patch rsyslog

#rcsyslog restart

Y volvemos a lanzar hping3 con los parámetros que mataron el servidor, pero esta vez vemos:


Esta vez el servidor no hice overflow y sigue funcionando perfectamente. Por lo tanto el parche funciona.

Bueno, esto ya se extendió demasiado, posiblemente esta vulnerabilidad pueda ser explotada para hacer cosas mas peligrosas, pero a mi gusto es suficiente con poder denegar un servicio que podría ser crucial para una organización.

El parche creo que se tomó 1 semana en salir, y en ese tiempo todos los rsyslog que vagaban por el mundo eran vulnerables a algo tan secillo como esto... me gustaría creer que no queda ninguno sin actualiazr... ME GUSTARÍA!!!