2. Analizando datos

Expiración

En un servidor con más de cien cuentas se puede volver imposible llevar a mano el registro de qué cuentas están activas, qué cuentas ya expiraron y hace cuánto tiempo. Este sencillo script de menos de 70 líneas puede generar un reporte periódico al administrador del estado de las cuentas en su sistema.
Por simplicidad, el script está construído alrededor de la estructura fija del archivo /etc/shadow. Transportarlo a otros sistemas, sin embargo, debe ser trivial.

Consideraciones de seguridad

Estamos ante uno de los mayores riesgos de seguridad: un programa que debe correr con privilegio de root, el único usuario con derecho de leer el archivo /etc/shadow. Es muy importante tomar todas las precauciones; el script corre con use strict y en modo -Tw (tainted y warnings). Fuera de la lectura inicial no tiene ninguna interacción con el mundo; la salida la envía a STDOUT. Esto es para evitar que alguien se aproveche de sus elevados atributos para atacar al sistema.
Este script está pensado para correr desde el crontab de root.

Funcionamiento general

El funcionamiento de este script es muy sencillo. Tras cargar el módulo Date::Calc y activar el pragma strict, iteramos sobre cada línea del archivo /etc/shadow, revisando su octavo campo (recuerden que el número base de un arreglo es 0), acomodándolo acorde a su estado respecto a la fecha actual en uno de cinco arreglos.Posteriormente, por medio de otra muy simple rutina, imprime cada uno de estos arreglos en órden. El programa nos reportará en este órden las cuentas próximas a expirar o recién expiradas, las que expiraron en el último mes, las que llevan ya más de un mes expiradas, las que no han expirado y las que no tienen fecha de expiración.

Funcionamiento: Obtención y comparación de la fecha

En la línea 12 vemos que:

  1. $dias=Delta_Days(1970,1,1,@fecha);

En los sistemas Unix, las fechas se guardan como el número de días transcurridos desde el 1 de enero de 1970. Las fechas de expiración en
/etc/shadow están en este mismo formato, por lo cual compararlas posteriormente será únicamente comparar dos valores numéricos.

Funcionamiento: Generación del reporte

Al poblar los cinco arreglos pueden haber notado que lo hacemos con una referencia a un arreglo [$login,$dias]. Esto es para pasar dos datos en una sola variable escalar.
Al recibir estos datos en &reporta los separamos expresamente en $login y $dias para evitar que los datos nos lleguen en un formato no
previsto y afecten de alguna manera la ejecución y seguridad de nuestro programa.

  1. #!/usr/bin/perl -Tw
  2. # expira.pl
  3. #
  4. # Reporta las fechas de expiracion de las cuentas de los usuarios
  5. #
  6. use strict;
  7. use Date::Calc qw(Today Delta_Days);
  8. my ($dias,@fecha,$file,@linea,@nuncaExpira,@expiraPronto,@expiraMes,@expiraAntes,@noExpirada);
  9. @fecha=Today;
  10. $dias=Delta_Days(1970,1,1,@fecha);
  11. $file='/etc/shadow';
  12. # Solo root puede abrir el archivo /etc/shadow
  13. open(FILE,$file) or die 'Es necesario ejecutar este script como root';
  14. while (@linea=split(/:/,<FILE>)) {
  15. if ($linea[7] eq '' || $linea[7] < 0) {
  16. # La cuenta no expira
  17. push (@nuncaExpira,[$linea[0],$linea[7]]);
  18. } elsif (($linea[7]-5)<$dias && ($linea[7]+5)>$dias) {
  19. # La cuenta expira o expiro en estos dias
  20. push (@expiraPronto,[$linea[0],$linea[7]]);
  21. } elsif ($linea[7]+30>=$dias && $linea[7]<$dias) {
  22. # La cuenta expiro hace menos de 30 dias
  23. push (@expiraMes,[$linea[0],$linea[7]]);
  24. } elsif ($linea[7]<$dias) {
  25. # La cuenta expiro hace mas de un mes
  26. push (@expiraAntes,[$linea[0],$linea[7]]);
  27. } else {
  28. # La cuenta no ha expirado
  29. push (@noExpirada,[$linea[0],$linea[7]]);
  30. }
  31. }
  32. close(FILE);
  33. print "\n";
  34. print '='x37,"\n Cuentas que expiran en estos dias \n",'='x37,"\n";
  35. &reporta(\@expiraPronto);
  36. print '='x37,"\n Cuentas que expiraron el ultimo mes \n",'='x37,"\n";
  37. &reporta(\@expiraMes);
  38. print '='x37,"\n Cuentas que expiraron hace tiempo \n",'='x37,"\n";
  39. &reporta(\@expiraAntes);
  40. print '='x37,"\n Cuentas que no han expirado \n",'='x37,"\n";
  41. &reporta(\@noExpirada);
  42. print '='x37,"\n Cuentas que no expiran \n",'='x37,"\n";
  43. &reporta(\@nuncaExpira);
  44. sub reporta {
  45. my ($login,$dias);
  46. foreach (@{$_[0]}) {
  47. ($login,$dias)=@{$_};
  48. printf(" %-15s %6s\n",$login,$dias);
  49. }
  50. }

Atributos

Es muy importante mantener verificada la integridad de nuestro sistema. Si no llevamos registro de los cambios efectuados a nuestros equipos, un atacante podrá entrar y modificar nuestro sistema sin ninguna dificultad, y será imposible determinar el alcance de los daños provocados por él.
Hay varios paquetes que hacen algo parecido a este script, el más conocido de ellos Tripwire. Todos ellos, sin embargo, pueden ser perfeccionados de más de una manera. Ilustramos aquí el funcionamiento del módulo central.

Dos versiones - El dilema de la firma

Presentamos dos versiones del programa - una que revisa únicamente los atributos de los archivos y una que calcula las firmas MD5 de cada
uno de ellos. ¿Por qué?

  • Seguridad- Si firmamos cada archivo, nos es necesario poder leer cada uno de ellos. Muchos archivos claves del sistema requieren tener root para leerlos. Si buscamos que un usuario cualquiera ejecute el programa, sólo podremos revisar los atributos.
  • Velocidad- Calcular la firma MD5 de un par de archivos es muy rápido. Sin embargo, calcular la de cientos o miles de archivos puede tomar mucho más tiempo del que estamos dispuestos a invertir. Cada usuario deberá poder elegir lo que juzgue mejor.

Consideraciones de seguridad

Aparte del punto de seguridad antes mencionado, nuestro programa debe poder listar todos los directorios a los que entre, ya que de ellos sacará la información necesaria. Si tenemos directorios protegidos con permisos 700, por ejemplo, nuestro programa no podrá entrar a este a menos que corra como el dueño del directorio.
Si decidimos no correr la versión MD5 del programa, un atacante con cierto conocimiento del sistema puede sin ningún problema alterar los
datos para cubrir sus huellas. Esta es una protección contra cambios no deseados y contra script kiddies.

Funcionamiento general

El programa revisa primero que nada si fue llamado en modo de creación de la base de datos o de revisión del sistema. En el primer caso, es llamado para únicamente procesar un archivo. La operación es muy sencilla - Imprime una línea con las características que stat arroja sobre el archivo. Esta línea la podemos guardar en nuestra base de datos.
Al verificar archivos, el programa itera sobre el archivo con los datos y, si hay alguna discrepancia con el estado actual, lo reporta de
una manera fácil de leer y comprender.

Funcionamiento general: Ejemplo de salida

  1. [gwolf@server gwolf]$ ./atributos.pl -p /home/gwolf/archivo_prueba.txt
  2. /home/gwolf/archivo_prueba.txt|1811|246064|33188|2|1160|0|1035916|2543|973982482|973982482|8192|6
  3. [gwolf@server gwolf]$ ./atributos.pl -p /home/gwolf/archivo_prueba.txt > database.ck
  4. [gwolf@server gwolf]$ ./atributos.pl -c database.ck
  5. [gwolf@server gwolf]$ echo 'modificando' >> archivo_prueba.txt
  6. [gwolf@server gwolf]$ ./atributos.pl -c database.ck
  7. /home/gwolf/archivo_prueba.txt:
  8. size is now 2918 (should be 2907)
  9. mtime is now 973982824 (should be 973982482)
  10. ctime is now 973982824 (should be 973982482)

Funcionamiento: Procesando opciones

En el script utilizamos el módulo Getopt::Std. Con él, las opciones de línea de comando que son especificadas con el comando

  1. getopt('p:c:');</code
  2. son recibidas en las variables <code>$opt_p
y $opt_c.

Funcionamiento: stat

Para conseguir los atributos de un archivo, no hay nada más útil que stat. Esta función nos regresa el siguiente arreglo:

  • dev Dispositivo físico
  • ino Número de i-nodo
  • mode Tipo y permisos del archivo
  • nlink Número de ligas duras
  • uid UID
  • gid GID
  • rdev Identificador de dispositivo (archivos especiales)
  • size Longitud total
  • atime Último acceso (formato epoch)
  • mtime Última modificación (formato epoch)
  • ctime Tiempo de modificación del inodo (formato epoch)
  • blksize Tamaño de bloques preferido para el sistema de archivos
  • blocks Número de bloques utilizados

  1. #!/usr/bin/perl -Tw
  2. use Getopt::Std;
  3. # We use this for prettier output later in &amp;printchanged()
  4. @statnames = qw(dev ino mode nlink uid gid rdev size mtime ctime blksize blocks);
  5. getopt('p:c:');
  6. die &quot;Usage: $0 [-p &lt;filename&gt;|-c &lt;filename&gt;]\n&quot; unless ($opt_p or $opt_c);
  7. if ($opt_p) {
  8. die &quot;Unable to stat file $opt_p: $!\n&quot; unless (-e $opt_p);
  9. print $opt_p,&quot;|&quot;,join('|',(lstat($opt_p))[0..7,9..12]),&quot;\n&quot;;
  10. exit;
  11. }
  12. if ($opt_c) {
  13. open(CFILE,$opt_c) or die &quot;Unable to open check file $opt_c: $!\n&quot;;
  14. while (&lt;CFILE&gt;){
  15. chomp;
  16. @savedstats = split('\|');
  17. die &quot;Wrong number of fields in line beginning with $savedstats[0]&quot; unless ($#savedstats == 12);
  18. @currentstats = (lstat($savedstats[0]))[0..7,9..12];
  19. # print the changed fields only if something has changed
  20. &amp;printchanged(\@savedstats,\@currentstats) if (&quot;@savedstats[1..12]&quot; ne &quot;@currentstats&quot;);
  21. }
  22. close(CFILE);
  23. }
  24. # iterates through attributes lists and prints any changes between
  25. # the two
  26. sub printchanged{
  27. my ($saved,$current) = @_;
  28. # print the name of the file after popping it off of the array read
  29. # from the check file
  30. print shift @{$saved},&quot;:\n&quot;;
  31. for (my $i = 0 ; $i &lt; $#{$saved} ; $i++) {
  32. if ($saved-&gt;[$i] ne $current-&gt;[$i]) {
  33. print &quot;\t&quot;.$statnames[$i].&quot; is now &quot;.$current-&gt;[$i];
  34. print &quot; (should be &quot;.$saved-&gt;[$i].&quot;)\n&quot;;
  35. }
  36. }
  37. }

Atributos y MD5

Es muy importante mantener verificada la integridad de nuestro sistema. Si no llevamos registro de los cambios efectuados a nuestros equipos, un atacante podrá entrar y modificar nuestro sistema sin ninguna dificultad, y será imposible determinar el alcance de los daños provocados por él.
Hay varios paquetes que hacen algo parecido a este script, el más conocido de ellos Tripwire. Todos ellos, sin embargo, pueden ser perfeccionados de más de una manera. Ilustramos aquí el funcionamiento del módulo central.

Dos versiones - El dilema de la firma

Presentamos dos versiones del programa - una que revisa únicamente los atributos de los archivos y una que calcula las firmas MD5 de cada
uno de ellos. ¿Por qué?

  • Seguridad- Si firmamos cada archivo, nos es necesario poder leer cada uno de ellos. Muchos archivos claves del sistema requieren tener root para leerlos. Si buscamos que un usuario cualquiera ejecute el programa, sólo podremos revisar los atributos.
  • Velocidad- Calcular la firma MD5 de un par de archivos es muy rápido. Sin embargo, calcular la de cientos o miles de archivos puede tomar mucho más tiempo del que estamos dispuestos a invertir. Cada usuario deberá poder elegir lo que juzgue mejor.

Consideraciones de seguridad

Aparte del punto de seguridad antes mencionado, nuestro programa debe poder listar todos los directorios a los que entre, ya que de ellos sacará la información necesaria. Si tenemos directorios protegidos con permisos 700, por ejemplo, nuestro programa no podrá entrar a este a menos que corra como el dueño del directorio.
Si decidimos no correr la versión MD5 del programa, un atacante con cierto conocimiento del sistema puede sin ningún problema alterar los
datos para cubrir sus huellas. Esta es una protección contra cambios no deseados y contra script kiddies.

Funcionamiento general

El programa revisa primero que nada si fue llamado en modo de creación de la base de datos o de revisión del sistema. En el primer caso, es llamado para únicamente procesar un archivo. La operación es muy sencilla - Imprime una línea con las características que stat arroja sobre el archivo. Esta línea la podemos guardar en nuestra base de datos.
Al verificar archivos, el programa itera sobre el archivo con los datos y, si hay alguna discrepancia con el estado actual, lo reporta de
una manera fácil de leer y comprender.

Funcionamiento general: Ejemplo de salida

  1. [gwolf@server gwolf]$ ./atributos.pl -p /home/gwolf/archivo_prueba.txt
  2. /home/gwolf/archivo_prueba.txt|1811|246064|33188|2|1160|0|1035916|2543|973982482|973982482|8192|6
  3. [gwolf@server gwolf]$ ./atributos.pl -p /home/gwolf/archivo_prueba.txt &gt; database.ck
  4. [gwolf@server gwolf]$ ./atributos.pl -c database.ck
  5. [gwolf@server gwolf]$ echo 'modificando' &gt;&gt; archivo_prueba.txt
  6. [gwolf@server gwolf]$ ./atributos.pl -c database.ck
  7. /home/gwolf/archivo_prueba.txt:
  8. size is now 2918 (should be 2907)
  9. mtime is now 973982824 (should be 973982482)
  10. ctime is now 973982824 (should be 973982482)

Funcionamiento: Procesando opciones

En el script utilizamos el módulo Getopt::Std. Con él, las opciones de línea de comando que son especificadas con el comando
getopt('p:c:');
son recibidas en las variables $opt_p y $opt_c.

Funcionamiento: stat

Para conseguir los atributos de un archivo, no hay nada más útil que stat. Esta función nos regresa el siguiente arreglo:

  • dev Dispositivo físico
  • ino Número de i-nodo
  • mode Tipo y permisos del archivo
  • nlink Número de ligas duras
  • uid UID
  • gid GID
  • rdev Identificador de dispositivo (archivos especiales)
  • size Longitud total
  • atime Último acceso (formato epoch)
  • mtime Última modificación (formato epoch)
  • ctime Tiempo de modificación del inodo (formato epoch)
  • blksize Tamaño de bloques preferido para el sistema de archivos
  • blocks Número de bloques utilizados

  1. #!/usr/bin/perl -Tw
  2. use Getopt::Std;
  3. use Digest::MD5 qw(md5);
  4. # We use this for prettier output later in &amp;printchanged()
  5. @statnames = qw(dev ino mode nlink uid gid rdev size mtime ctime blksize blocks md5);
  6. getopt('p:c:');
  7. die &quot;Usage: $0 [-p &lt;filename&gt;|-c &lt;filename&gt;]\n&quot; unless ($opt_p or $opt_c);
  8. if ($opt_p) {
  9. die &quot;Unable to stat file $opt_p: $!\n&quot; unless (-e $opt_p);
  10. open(F,$opt_p) or die &quot;Unable to open $opt_p: $!\n&quot;;
  11. $digest = Digest::MD5-&gt;new-&gt;addfile(F)-&gt;hexdigest;
  12. close(F);
  13. print $opt_p,&quot;|&quot;,join('|',(lstat($opt_p))[0..7,9..12]),&quot;|$digest\n&quot;;
  14. exit;
  15. }
  16. if ($opt_c) {
  17. open(CFILE,$opt_c) or die &quot;Unable to open check file $opt_c: $!\n&quot;;
  18. while (&lt;CFILE&gt;){
  19. chomp;
  20. @savedstats = split('\|');
  21. die &quot;Wrong number of fields in line beginning with $savedstats[0]&quot; unless ($#savedstats == 13);
  22. @currentstats = (lstat($savedstats[0]))[0..7,9..12];
  23. open(F,$savedstats[0]) or die &quot;Unable to open $opt_c: $!\n&quot;;
  24. push(@currentstats,Digest::MD5-&gt;new-&gt;addfile(F)-&gt;hexdigest);
  25. close(F);
  26. # print the changed fields only if something has changed
  27. &amp;printchanged(\@savedstats,\@currentstats) if (&quot;@savedstats[1..13]&quot; ne &quot;@currentstats&quot;);
  28. }
  29. close(CFILE);
  30. }
  31. # iterates through attributes lists and prints any changes between
  32. # the two
  33. sub printchanged{
  34. my ($saved,$current) = @_;
  35. # print the name of the file after popping it off of the array read
  36. # from the check file
  37. print shift @{$saved},&quot;:\n&quot;;
  38. for (my $i = 0 ; $i &lt; $#{$saved} ; $i++) {
  39. if ($saved-&gt;[$i] ne $current-&gt;[$i]) {
  40. print &quot;\t&quot;.$statnames[$i].&quot; is now &quot;.$current-&gt;[$i];
  41. print &quot; (should be &quot;.$saved-&gt;[$i].&quot;)\n&quot;;
  42. }
  43. }
  44. }