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
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
/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
}
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
}
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
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
[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.