13 noviembre 2014

Cifrando los logs de Apache con libCryptoLog

En muchas ocasiones sucede que es necesario tener una política de cifrado de logs y no siempre es fácil 'convencer' a los servidores para que guarden la información cifrada.

Para abordar ese tipo de escenarios he liberado la versión 0.1 (mini-beta?) de libCryptoLog que permite manipular 'al vuelo' la forma en la que cualquier servidor guarda los logs. Y digo cualquiera porque en realidad, si bien este post va de Apache, el método que voy a explicar sirve para cualquier otro servicio (Postfix, Nginx ...).

De lo que se trata es de 'convencer' al servidor en cuestión de que toda información que vuelque al fichero que nos interese cifrar se almacene cifrada desde el minuto 0.

Después de pensar y re-pensar una forma que me convenciese para abordar este problema, programé libCryptoLog con la idea de que fuese 'plugeable', es decir, en vez de hardcodear en la librería las rutinas de cifrado, he decidido que eso caiga en un 'helper' externo para que cualquiera pueda adaptarlo fácilmente a sus requerimientos.

El funcionamiento es fácil: Por lo general, cuando Apache o cualquier otro servidor van a escribir a disco, suelen emplear dos funciones: fprintf() o write(), en el caso de Apache el 'meollo' está en write().

Como muchos de vosotros en este punto ya habréis deducido, lo que estoy haciendo es 'hookear' ambas funciones y alterar la forma en la que procesan los datos que van a almacenar a fichero.

El procedimiento es muy fácil de entender:

1- fprintf() y write() toman una cadena y la vuelcan a un fichero

2- libCryptoLog intercepta esas llamadas y extrae la cadena de texto

3- Esa cadena de texto se le pasa a un programa externo para que la cifre, le ponga colores o lo que sea

4- libCryptoLog toma el resultado de ese programa y ES ESO lo que almacena empleando fprintf o write

De esa forma los logs, antes de que 'toquen disco' ya están cifrados.

Este modelo permite que cualquiera, en cualquier lenguaje, programe sus propios helpers para decidir como transformar la cadena de texto. Junto con la librería van unos cuantos helpers para cifrar logs con RSA.

Vamos a ver un ejemplo con Apache en Centos 6.5:

Lo primero es descargar la librería desde aquí y descomprimirla con:

# tar -xvzf libCryptoLog.tgz

Para usar los helpers escritos en Perl, es necesario tener disponible la librería perl-Crypt-RSA, la instalamos

# yum -y install perl-Crypt-RSA

Una vez instalada, ya podemos generar nuestras claves publica y privada, es MUY CONVENIENTE que la clave privada no esté en el sitio donde vamos a cifrar por razones más que obvias. Esa clave es la llave para acceder al contenido de los logs por lo que se ha de guardar en un sitio externo donde se vayan a descifrar los logs.

Ejecutando:

# perl rsacreate.pl

Vemos que en el directorio han aparecido dos nuevos ficheros: key.public y key.private

Copiamos key.public a /usr/local/etc/

# cp key.public /usr/local/etc/

Igualmente copiamos el 'helper' a /usr/local/bin

# cp rsacrypt.pl /usr/local/bin/

Este es el script que se encargará de cifrar los logs 'al vuelo' usando la clave pública.

Con esto hecho, toca compilar la librería. Aquí hay una parte que se debe configurar. La función write() toma un ID como parámetro para saber en que descriptor debe escribir. No se puede manipular todas las llamadas a write() porque esa función se usa para muchas cosas, es necesario determinar los IDs que corresponden a los ficheros que vamos a cifrar.

Para obtener esta información debemos buscar un PID de Apache y consultar con lsof los IDs de los ficheros que tiene abiertos.

# ps aux | grep -i httpd

root      1508  0.0  1.6 135832 17328 ?        Ss   09:26   0:00 /usr/sbin/httpd
apache    1540  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1541  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1542  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1543  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1544  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1545  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1546  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd
apache    1547  0.0  0.6 135832  6932 ?        S    09:26   0:00 /usr/sbin/httpd

Y luego

# lsof -p 1508

httpd   1508 root    0r   CHR    1,3      0t0   3903 /dev/null
httpd   1508 root    1w   CHR    1,3      0t0   3903 /dev/null
httpd   1508 root    2w   REG  253,0     3522 398650 /var/log/httpd/error_log
httpd   1508 root    3r   CHR    1,9      0t0   3908 /dev/urandom
httpd   1508 root    4u  sock    0,6      0t0  10395 can't identify protocol
httpd   1508 root    5u  IPv6  10396      0t0    TCP *:http (LISTEN)
httpd   1508 root    6r  FIFO    0,8      0t0  10489 pipe
httpd   1508 root    7w  FIFO    0,8      0t0  10489 pipe
httpd   1508 root    8w   REG  253,0   265970 398649 /var/log/httpd/access_log

Justo al final podemos ver qué /var/log/httpd/error_log y
/var/log/httpd/access_log tienen asociados los IDs 2 y 8. Esto se puede ver en la cuarta columna en formato numero+w, en este caso 2w y 8w

Como solo queremos que estos dos ficheros queden cifrados (son los que guardan la información sensible ...) editamos el fichero libCryptoLog.c y en la parte de:

int filedesyes[2] = {3, 10};

La cambiamos a 

int filedesyes[2] = {2, 8};

Para que la librería sepa cuales llamadas a write debe modificar y cuales simplemente las debe dejar pasar sin hacer nada.

Una vez hecho eso, compilamos:

# gcc -Wall -fPIC -shared -o libCryptoLog.so libCryptoLog.c -ldl -lssl

Y copiamos nuestra flamante nueva librería en /usr/local/lib

# cp libCryptoLog.so /usr/local/lib/

¡¡ Ya casi casi estamos !!

Ahora, para que se produzca el 'hooking' tenemos que instruir al fichero de arranque de Apache para que defina la variable LD_PRELOAD correctamente en los procesos que genere.

# vi /etc/init.d/httpd

En la parte de start localizamos algo como

start() {
        echo -n $"Starting $prog: "
        LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && touch ${lockfile}
        return $RETVAL
}

Y debemos modificarla para que quede algo así (he puesto en negrita lo que he añadido)

start() {
        echo -n $"Starting $prog: "
        LD_PRELOAD=/usr/local/lib/libCryptoLog.so LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && touch ${lockfile}
        return $RETVAL
}

Y si todo ha ido bien, en cuanto hagamos un stop / start nuestros ficheros de log pasarían a quedar cifrados.

# service httpd stop
# service httpd start

Si hacemos un tail a /var/log/http/error.log ya podemos ver que Apache está guardando la información cifrada:

BEGINCRYPTO
gRNmfi/yS9Vaya37VJ7sM+iZtoYDG976SWPa4XTLnPGccBTd56J8Bk0uLZyK86vopcjdKp2JPDr7
oHWk/TKA00IStIgvTofUH9DeZGepqikIkjJg9wylAJ0ROjpcerozOX1LQWuj+ZoOxRu7K+UIeQmc
389SjDAyqNs/U8UHc75ntbVHy/A1e95fWUAHnkcD/1au463ugNHQmCJoSHA4NgwhDmwUJLafWSKr
T/L6BaOsruxDtkUqu0gBfROadVuc9oALSdRSc5WqA3T5HuS10a49szZ5zedqtQJiQFjikJCRo/v6
tzYHHs3Es+8yfpZti/l3pChW8+zHCxuPRKNccg==
ENDCRYPTO
BEGINCRYPTO
VB68V3MyG7yNHfYc8UR69ZbaC4ztBkOigWnKZzlKTMiXNdSBFEJ++TPKQXUFo4j8AfrgQPL6DQQ8
nd0yoMSaA3ojq+MvBY5cSLstVeEGaIJSXRboZMGyq6UpfOAqvWLvd48w63ND9cKKDBkEQcfUM3a7
S5KPss/qqSKYcsSHsqk=
ENDCRYPTO
BEGINCRYPTO
DN0d0oa8DJrVj7GGuiGUuhRMc8bDIPGUL39Ae51YFwZl1mRoq5EgipcTxyPThTngw7rOobUGrCaS
YY/MlO4FV4HGAsBWsQksfqYzsgbC7Lv+Ek4oe+01rhJ2L70BKPWPiRdr9oQHsWcL8aTDtLj8X/H4
R3XJ76mvNUSOPAPjijE2WZiAIRYmU+0fzTrbJTYaV3GrGYm2rWkoFQu7aImQHqWRsnSs9k9RmLij
3KoX/MpBBd+a0hOhBsZQmLf915VJgp5E2zrqXMwutdyS3+UJ7o+lesK8LfC2jl40aYvVZXZXNPv+
uDsNbkPJIRzXpfmwnR5KtMFcNOdEJ+1378BRrQ==
ENDCRYPTO

 
Ahora queda la parte final ¿y como revierto el cifrado para tener el log original? Para eso, hay un script llamado rsadecrypt.pl que toma como parámetro un fichero entrada y otro salida, en este caso entrada sería error.log y salida error2.log, quedando en este último el formato descifrado.

# perl rsadecrypt.pl /var/log/httpd/error_log error2.log

Si hacemos un tail en error2.log

[Fri Jun 06 09:55:34 2014] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Fri Jun 06 09:55:34 2014] [notice] Digest: generating secret for digest authentication ...
[Fri Jun 06 09:55:34 2014] [notice] Digest: done
[Fri Jun 06 09:55:34 2014] [notice] Apache/2.2.15 (Unix) DAV/2 PHP/5.3.3 mod_perl/2.0.4 Perl/v5.10.1 configured -- resuming normal operations

Vemos que lo que antes era base64 cifrada, ahora es plain text :)

Evidentemente no todo es gratis, el añadir este 'extra' de cifrado penaliza bastante el rendimiento porque las operaciones RSA son 'pesadas' y además desde un script aun más. 

Hay que tener mucho cuidado en qué servidores se activa esta medida de seguridad y si tienen la capacidad adecuada para soportar el cambio.

Como última cosa, reiterar que en este ejemplo se están usando mis scripts para tratar los logs, en el código de la librería se puede ver el comando definido 

char *command = "perl /usr/local/bin/rsacrypt.pl \"";

Pero ahí puede ir cualquier cosa que trate el texto de los logs.

7 comments :

Rogger Ortega dijo...

Me lo guardo, para leer con calma que muy bien lo merece.
Saludos.

Yago Jesus dijo...

Muchas gracias, si algo no hace lo que se supone que debe hacer, por favor avísame :)

masticover dijo...

¡Enhorabuena! Pedazo de curro y herramienta fantástica! A favoritos :)

Yago Jesus dijo...

Muchas gracias !!

Kayla Marrie dijo...

Quiero agradecer al Dr. Olokum para llevar alegría y felicidad a mi relación y mi familia. Perdí a mi novio, y yo requería ayuda hasta que me encontré con el Dr. Olokum el verdadero hechicero, Y él me aseguró que voy a tener mi novio de vuelta en dos días después de que el hechizo se ha fundido. dos días después, mi teléfono sonó, y así sorprendentemente, era mi novio, que no me ha llamado por mucho tiempo, e hizo una apología de la rotura del corazón, y me dijo que él está dispuesto a dedicar el resto de su vida conmigo. ola princetess lo soltó hasta saber lo mucho que me amaba y quería que él .. en este momento yo y mi novio es vivir una vida feliz y nuestro amor es ahora más fuerte que la forma en que eran incluso antes de nuestro descanso up..All agradecimiento a DR Olokum por el excesivo trabajo que ha hecho por mí. A continuación se muestra que la dirección de correo electrónico en la situación en la que está siendo sometido a una rotura del corazón, y te aseguro que a medida que la mina ha hecho por mí, ella definitivamente le ayudará también. Email: LAVENDERLOVESPELL@YAHOO.COM

nanai dijo...

Muy arriesgado el usar unicamente el numero de filedes. Me parece un poco sin sentido usar el base64 en openssl en la lib y luego tener que recurrir a un helper en perl para hacer el cifrado (ademas de deshacer y hacer base64 nuevamente en el helper)!

Yago Jesus dijo...

Hola, lo de arriesgado por el ID es relativo, si la cargas en ld.so.preload para *todos* los procesos claramente es un suicidio, por eso propongo cargarlo por variable y por cada proceso al que previamente has buscado los IDs, tengo varias instalaciones con meses de uptime sin problemas. Salvo que me pongas un ejemplo y demuestres lo contrario, yo diría que los IDs son fijos

Lo del base64 es simple y llanamente para curarme en salud y saber que los datos que llegan al helper son confiables y no llevan veneno. Es una medida de seguridad ya que voy a usar un comando externo y claro, eso suele ser problemático cuando toma parámetros.

Respecto a meter o no meter las rutinas de cifrado en la librería, ya he explicado los motivos, principalmente la flexibilidad de que cualquiera pueda crear un helper en C, Python o Perl y tratar el log como quiera, sin estar 'vendido' a que yo, por ejemplo, cifre con RSA y tu quieras usar otro algoritmo.