Información blog

Linux, tutoriales, noticias, sistemas, redes y seguridad informática, entre otras cosas.

Mostrando entradas con la etiqueta Desarrollo. Mostrar todas las entradas
Mostrando entradas con la etiqueta Desarrollo. Mostrar todas las entradas

lunes, 19 de febrero de 2018

Cómo registrar qué USBs se han conectado en Linux

La recopilación de información es cada día más importante; en servidores siempre lo ha sido, pero en los equipos de sobremesa ha ido cobrando importancia paulatinamente  y hoy en día se ha demostrado que es importante tener registrada toda la información por si en un futuro nos pudiese llegar a ser útil. Hoy quiero hablar sobre el registro de USBs en Linux. Linux tiene la gran ventaja de tener una enorme cantidad de logs, pero los logs no siempre te registran todos los datos que uno desea y corremos el peligro de que dichos logs sean borrados o se roten mediante utilidades tales como logrotate; utilidades especialmente comunes en servidores. Por eso puede ser interesante tener una herramienta corriendo en segundo plano cuya única función sea registrar qué USBs se han conectado/desconectado, la hora en la que lo han hecho, datos útiles tales como el número de serie y de paso, registrar su actividad a ser posible.

portada_registro_usb

En este caso no optaremos a herramientas de terceros sino que tiraremos de conceptos de Linux y los aglutinaremos en un script que automatice todo ello... Además, en este caso, no almacenaremos la información en un fichero en texto plano, sino que vamos a almacenarla en una mini base de datos en formato SQLite... El motivo por el cual se ha optado por dicha opción es simple: SQLite requiere instalar pocas herramientas en el equipo lo cual es perfecto en equipos de sobremesa; además es una base de datos que consume muy pocos recursos por parte del equipo y al mismo tiempo tiene la gran ventaja de seguir siendo una base de datos basada en el lenguaje SQL, con lo cual podríamos hacer consultar de su información mediante consultas SQL mediante SELECTs con sus respectivos filtros, etc... Haciendo que la consulta de la información en el futuro sea muchísimo más eficiente, con la contrapartida de que se requieren unos conocimientos básicos de SQL para poder sacarle partido.

Teniendo esto en cuenta, comencemos con la instalación de las dependencias necesarias para que nuestra solución funcione... Dependiendo de qué distribución tengamos y las herramientas que tengamos instaladas, puede ser que ya tengamos todo lo necesario, pero para asegurarnos haremos:
apt-get install sqlite
Teniendo todo preparado, pasaríamos a crearnos primero la mini base de datos que almacenará la información de los USBs. En mi caso en particular he creado la base de datos en /var y la he llamado listaUsbs.db con lo que su creación sería tal que así:
sqlite /var/listaUSBs.db
Esto no solo crearía la base de datos, sino que estaríamos dentro de ella, con lo que pasaríamos a crear la tabla en cuestión. En mi caso he llamado a la tabla listausbs y su creación (junto con sus respectivas columnas) sería la siguiente:
CREATE TABLE lista_usbs (NOMBRE,FECHA_DETECT,FECHA_DESCONEX,
IDVENDOR,IDPRODUCT,MANUFACT,IPRODUCT,SERIAL);
En esta base de datos guardaremos toda la información importante tal como el nombre completo, la fecha de detección del USB, el fabricante y el numero de serie... Información que puede sernos útil en el futuro. Con esto claro, podemos crear un script en el que por un lado monitoricemos en tiempo real toda la actividad USB mediante tcpdump, y por otro lado también podemos tener constancia de qué USB se ha conectado en qué momento; esto lo podemos lograr mediante el siguiente script, al cual yo he llamado registrador_usbs.sh y que ha sido probado en Debian 8:


#!/bin/bash

#CAPTURAMOS TODO EL TRAFICO USB
modprobe usbmon
nohup tcpdump -i usbmon1 -w /tmp/captura.cap &
while true;
do
        #COMPROBAMOS SI SE HA CONECTADO ALGUN USB NUEVO
        if [ ! -f /tmp/listausbs2.txt ];
        then
                touch /tmp/listausbs2.txt
        fi
        lsusb > /tmp/listausbs.txt
        diff /tmp/listausbs.txt /tmp/listausbs2.txt
        if [ $? -eq 1 ]
        then
                diff /tmp/listausbs.txt /tmp/listausbs2.txt > /tmp/cambio.txt
                cat /tmp/cambio.txt |tail -n +2 > /tmp/cambio2.txt
                while read CAMBIO;
                do
                echo ${CAMBIO} > /tmp/cambio3.txt
                cat /tmp/cambio3.txt |grep "<"
                if [ $? -eq 0 ];
                then
                        #SI ES UNA CONEXION NUEVA LA REGISTRAMOS
                        NOMBRE="$( cat /tmp/cambio3.txt |cut -d '<' -f 2 |cut -d ':' -f 2- | cut -d 'D' -f 2- )"
                        echo $NOMBRE
                        lsusb -D /dev/bus/usb/$(lsusb |grep "${NOMBRE}" |awk {'print $2'})/$(lsusb |grep "${NOMBRE}" |awk {'print $4'} |cut -d ":" -f 1) > /tmp/usb_detallado.txt
                        FECHA_DETECT=$(date)
                        FECHA_DESCONEX=''
                        IDVENDOR=$(cat /tmp/usb_detallado.txt |grep idVendor |awk '{for (i=2; i<NF; i++) printf $i " "; if (NF >= 2) print $NF; }')
                        IDPRODUCT=$(cat /tmp/usb_detallado.txt |grep idProduct |awk '{for (i=2; i<NF; i++) printf $i " "; if (NF >= 2) print $NF; }')
                        MANUFACT=$(cat /tmp/usb_detallado.txt |grep iManufacturer |awk '{for (i=2; i<NF; i++) printf $i " "; if (NF >= 2) print $NF; }')
                        IPRODUCT=$(cat /tmp/usb_detallado.txt |grep iProduct |awk '{for (i=2; i<NF; i++) printf $i " "; if (NF >= 2) print $NF; }')
                        SERIAL=$(cat /tmp/usb_detallado.txt |grep iSerial |awk '{for (i=2; i<NF; i++) printf $i " "; if (NF >= 2) print $NF; }')
                        sqlite /var/listaUSBs.db "INSERT INTO lista_usbs VALUES ('${NOMBRE}','${FECHA_DETECT}','${FECHA_DESCONEX}','${IDVENDOR}','${IDPRODUCT}','${MANUFACT}','${IPRODUCT}','${SERIAL}');"
                        rm /tmp/usb_detallado.txt
                fi
                cat /tmp/cambio3.txt |grep ">"
                if [ $? -eq 0 ];
                then
                        #SI ES UNA DESCONEXION ACTUALIZAMOS LOS DATOS CORRESPONDIENTES
                        NOMBRE="$( cat /tmp/cambio3.txt |cut -d '>' -f 2 |cut -d ':' -f 2- | cut -d 'D' -f 2- )"
                        FECHA_DESCONEX=$(date)
                        sqlite /var/listaUSBs.db "UPDATE lista_usbs SET FECHA_DESCONEX='${FECHA_DESCONEX}' WHERE FECHA_DESCONEX='' AND NOMBRE='${NOMBRE}';"
                fi
                done < /tmp/cambio2.txt
                rm /tmp/cambio.txt
                rm /tmp/cambio2.txt
                rm /tmp/cambio3.txt
        fi
        mv /tmp/listausbs.txt /tmp/listausbs2.txt
done

Con este script, solamente ejecutándolo en segundo plano estaríamos monitorizando en tiempo real toda la actividad mediante tcpdump, y registrándola en nuestra base de datos SQLite. Solamente habría que escribir:
./registrador_usbs.sh >/dev/null &
Dicha información podría ser consultada en el futuro mediante consultas SQL tales como:

Para ver todos los registros:
sqlite /var/listaUSBs.db "SELECT * FROM lista_usbs;"
Para ver un numero de serie concreto:
sqlite /var/listaUSBs.db "SELECT * FROM lista_usbs WHERE 
SERIAL='3 20060413092100000';"
Para eliminar todos los registros:
sqlite /var/listaUSBs.db "DELETE FROM lista_usbs;"

Con ello tendríamos todo monitorizado y registrado en nuestro equipo ganando un gran control sobre la actividad USB en nuestro equipo. Obviamente dicho script sería solamente un ejemplo teórico que puede ser modificado a nuestro gusto para que se adapte lo mejor posible a nuestras necesidades.

Espero que os haya resultado útil.

Saludos.

miércoles, 11 de enero de 2017

Permisos especiales en Linux: Sticky bit, SUID y SGID

Hoy quiero traeros un concepto que si bien no es especialmente "desconocido" hay muchas personas que lo pasan por alto debido a su poco uso. En su día hablé sobre el concepto de los permisos de los ficheros en Linux con bastante profundidad; pero hoy quiero traeros un concepto derivado de éste que va un pequeño paso más allá: Los permisos especiales.

Permisos_especiales_portada

Estos permisos están diseñados tanto para garantizar un cierta "flexibilidad" a la hora de ejecutar algunos binarios cómo para evitar posibles accidentes por parte de algunos usuarios... Permisos especiales que, si bien son pocos, conviene conocer adecuadamente. En concreto habrían 3 tipos de permisos especiales.


Sticky bit

Por un lado tendríamos el sticky bit; que se trata de un permiso especial que puede ser asignado tanto a ficheros como directorios. Este permiso puede ser muy útil en ficheros de cierta relevancia tales como los ficheros de log o ficheros importantes, ya que lo que permite es leer y escribir en el fichero y/o directorio en cuestión, pero no se podría ni renombrar ni borrar por nadie a excepción de dos usuarios: El creador de éste y root; lo que hace que a nivel de seguridad tengamos una cierta tranquilidad, ya que los usuarios pueden leer y escribir en el fichero con libertad pero jamás podrán alterar su nombre ni borrarlo. Este permiso se puede agregar de dos formas; a nivel directo o a nivel octal.

La forma directa sería:

Para añadir el permiso sticky bit:
chmod +t fichero

Para quitar el permiso sticky bit:
chmod -t fichero

A nivel octal lo veremos mejor más adelante con el fin de englobar todos los permisos especiales.


SUID

Por otro lado tendríamos el SUID o "Set Owner ID up on execution", cuya función sería que el usuario que ejecute el archivo, tendría exactamente los mismos permisos que el dueño de éste. Esto puede parecer algo simple pero no lo es; si por ejemplo tuviésemos un archivo con permisos de ejecución (para todos) y tuviese dicho SUID, cualquiera que lo ejecute lo estaría ejecutando como si fuese el dueño del script, lo que es muy útil y muy peligroso al mismo tiempo. Imaginemos que el script perteneciese a root y tuviese permisos de ejecución para todos y el SUID activado; además el contenido de dicho script , escrito en C y llamado test.c, sería algo como lo siguiente:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. main(){
  4.   printf("UID: %d, EUID: %d\n",getuid(),geteuid());
  5.   system("cat /etc/shadow");
  6. }

Obviamente el programa estaría compilado; es decir:

gcc test.c -o test

Al ejecutar lo que pasaría sería que mostraría los valores UID y EUID e intentaría ejecutar el comando "cat /etc/shadow". UID sería el User ID del usuario que generalmente sería valor a partir de 1000 (1000, 1001, 1002...) y el EUID sería el Effective User ID, valor que indica el User ID que se está usando para realizar la tarea en cuestión. Al no tener el permiso especial activo, ambos valores serían iguales y el comando cat diría que no tenemos permisos para ver el contenido del fichero. Ahora bien, vamos a activar el SUID:

chmod u+s test

Si lo ejecutásemos veríamos por un lado que el valor EUID ha cambiado a 0 (ya que el UID de root es ese) y que permite ejecutar el comando cat sobre el fichero shadow, lo cual es peligroso.

Esto implica que si bien este permiso es útil, hay que ser extremadamente peligroso con él ya que se puede utilizar como recurso para escalar privilegios. La forma de deshabilitar este permiso sería:

chmod u-s test


SGID

Es extremadamente parecido al SUID con la diferencia de que en este caso el SGID o "Set Group ID on execution" tomaría los permisos del grupo, no del usuario en sí. Aún así, si el grupo fuese root o uno grupo con privilegios de administrador, este permiso podría ser usado en favor de la escalada de privilegios.

Para añadir el SGID habría que escribir:
chmod g+s fichero

Mientras que para quitarlo sería:
chmod g-s fichero


Cambiando los permisos especiales a nivel Octal

En mi opinión, el conocer como cambiar los permisos especiales a nivel octal es mucho más útil que a nivel "normal" ya que ofrece muchas más potencia y flexibilidad el hacerlo de esta forma; no es obligatorio hacerlo así; es más es perfectamente viable hacer el cambio de permisos de las formas que he comentado, pero en mi opinión es muy útil conocer la forma de cambiar los permisos en octal.

Lo primero que habría que saber sería que cuando hacemos una asignación de permisos "normales" a nivel octal, lo hacemos mediante el comando chmod más tres dígitos que pueden oscilar entre 0 y 7; pues bien, el octal que asignaría los permisos especiales iría antes de dichos tres dígitos, haciendo que el comando chmod se tenga que hacer mediante el comando chmod más 4 dígitos. Por ejemplo:

chmod 1755 fichero

El 1 sería el octal que haría referencia a los permisos especiales y sería dicho octal el que habría que tener en cuenta a la hora de asignar los permisos especiales. El valor octal sería el mismo que el asignado para los permisos normales, es decir entre 0 y 7. He aquí una tabla muy explicita que explica exactamente qué significa cada número:

chmod_especiales

Gracias a esta tabla, podemos asignar los permisos especiales a nuestro antojo y poder asignar uno o varios permisos especiales con total libertad; pudiendo hacer cosas como éstas:

Otorgar sticky bit junto con los permisos 644:
chmod 1644 fichero

Activar el SUID y el SGID en el fichero junto con los permisos 755:
chmod 6755 fichero

Darle el sticky bit y el SUID a un fichero más los permisos 600:
chmod 5600 fichero


Como veis, los permisos especiales son muy útiles y al mismo tiempo peligrosos según el caso, pero es muy conveniente tenerlos presentes, ya sea para evitar escaladas de privilegios accidentales o para prevenir el borrado o renombrado accidental de un fichero.

Espero que os haya sido útil.

Saludos.

martes, 22 de noviembre de 2016

Procesos zombie en Linux; qué son y cómo "matarlos"

Cualquiera que haya tratado con terminales Linux con cierta regularidad, se ha encontrado alguna vez en su vida con procesos "especiales" que no importa lo que hagan con ellos que no son capaz de "morir"; son procesos que están ahí "muertos" y que no consumen memoria RAM ni CPU, pero que el sistema todavía reconoce como existentes y que no importa lo que se haga con ellos que se resisten a desaparecer... Dichos procesos son llamados procesos zombie y si bien no tienen que causar problemas de rendimiento, su sola aparición hace que pensemos que dicho proceso está activo y pueden dar lugar a confusiones; es por eso que lo ideal es que ninguno de dichos zombies pululen por nuestro sistema operativo, por muy inofensivos que éstos sean.

Zombie_proceso

Los procesos zombie siempre son procesos dependientes de un proceso "padre" que no ha sido capaz de finalizar correctamente sus procesos "hijo", haciendo que estos sigan existiendo en el sistema aún cuando no consuman recursos... Este fenómeno es fácilmente comprobable en cualquier consola de Linux; pero antes, para asegurarnos que efectivamente va a existir un proceso zombie, crearemos varios "zombies" nosotros mismos a modo de prueba de concepto. Para ello tendríamos que crear un pequeño programa escrito en c llamado, por ejemplo, zombiegen.c cuyo contenido sería:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main() {
  4.         pid_t pid;
  5.         int i=0;
  6.         while (1)  {
  7.                 pid = fork();
  8.                 if (pid > 0) {
  9.                         // Proceso padre
  10.                         printf("Padre %d\n",+ 1);
  11.                         sleep(1);
  12.                 } else {
  13.                         // Proceso hijo
  14.                         printf("Zombie %d\n",+ 1);
  15.                         exit(0);
  16.                 }
  17.         i++;
  18.         }
  19.         return 0;
  20. }

Después habría que compilar el programa mediante gcc tal que así:

gcc zombie.c -o zombie

Con esto haríamos que se creasen constantemente procesos zombie hasta que decidiésemos parar el programa. ¿Cómo podemos saber si el sistema tiene algún proceso zombie actualmente? Mediante el siguiente comando:

ps aux |grep defunct |grep -v grep

Al ser un bucle que crea procesos zombie constantemente podemos encontrarnos con algo como esto:

Lista_procesos_zombie

Si os fijáis bien en la línea en cuestión, veréis que una de sus columnas tiene una única letra; Z. Dicha Z significaría que dicho proceso tiene la etiqueta zombie, y que si bien no está operativo, dicho proceso en sí sigue existiendo en el sistema. Además veríamos también que todos los procesos finalizan con <defunct> que significaría que el proceso está parado, pero que todavía existe.

Ahora bien, ¿Cómo nos libramos de los zombies?  Tan simple como matando al padre de todos éstos, con lo que lo primero que tendríamos que ver es... ¿Quien es el padre de todos estos? Si conociésemos el PID (process ID) del padre, podríamos "matar" este para así automáticamente acabar con sus hijos... Esto puede parecer complejo pero es tan sencillo como hacer:

ps -eo pid,ppid,command | grep defunct |grep -v grep

Con esto listaríamos el numero de proceso de cada zombie, seguido del número de proceso de aquel que ha "creado" el zombie, junto con el comando.

En mi caso en particular me aparecería esto:

Padre_zombies

Si nos fijamos bien, veremos que todos comparten el mismo PPID (Parent Process ID) ... Es decir que todos dependen de dicho proceso para seguir existiendo así que únicamente tendríamos que acabar con éste proceso mediante un simple kill. En este caso tendríamos que detener el proceso con PID 4636.

kill -9 4636

Tras lo cual únicamente tendríamos que volver a ejecutar nuestro anterior comando para ver que efectivamente hemos acabado con todos ellos.

ps_sin_zombies

Como veis, el encontrar y acabar con estos procesos es muy sencillo; solamente hay que buscar procesos "defunct" y acabar con el proceso que los está generando... Obviamente para acabar con la raíz del problema habría que ver qué error de programación hay en el proceso padre, pero esto puede servirnos como una solución temporal para limpiar los procesos zombie hasta encontrar el verdadero origen de esta situación.

Espero que os haya resultado útil.

Saludos.

martes, 15 de noviembre de 2016

Cómo crear nuestro propio segundo factor de autenticación Linux

Anteriormente ya he hablado con respecto al concepto del segundo factor de autenticación, en concreto con la aplicada en las conexiones SSH.  Esta medida de seguridad ha ido ganando mucha relevancia durante los últimos años, y con razón pues añade una importante capa de seguridad extra a nuestros entornos... La cuestión está en que usando únicamente un segundo factor de autenticación en las conexiones SSH, hacemos que cualquiera que se loguee a nivel local no tenga restricciones para acceder al equipo, lo que puede resultar cómodo por un lado, pero inseguro por otro... Es por eso que hoy vamos a preparar un segundo factor de autenticación para cualquier login que se realice en Linux, ya sea local o remotamente. En concreto realizaremos un segundo factor de autenticación casero programado en Bash que, en mi opinión, le puede resultar útil a más de uno.

2fa_portada

Para ello lo primero que tenemos que tener en cuenta es donde podemos "interponer" nuestro segundo factor de autenticación... Es decir en qué parte del sistema podemos aplicar nuestro programa para que tanto los logins locales como remotos se vean afectados... El lugar más apropiado sería el fichero .bashrc alojado en la carpeta "home" de cada usuario, pues sabemos con toda seguridad que cualquier inicio de sesión ejecutará dicho programa y que dicho fichero es fácilmente editable... En mi caso en particular he aplicado el segundo factor de autenticación en el fichero /home/ivan/.bashrc; fichero al que le habría añadido está línea al final:

/usr/bin/2fa.sh

Esta línea simplemente significaría que, tras realizar todas las tareas correspondientes, ejecute el script 2fa.sh, script que de momento no existe (o que no debería existir) ya que dicho fichero va a ser fabricación nuestra. Empecemos a construir nuestro script paso a paso... Lo primero que tenemos que hacer es generar un código aleatorio que podamos usar en nuestra "nueva" autenticación... Con un código numérico tendría que ser suficiente, así que vamos a comenzar nuestro script con una pequeña función que genere un número aleatorio de 6 dígitos, si bien podemos hacer que dicho número sea mayor en caso de verlo necesario. Podemos escribir nuestro script con el editor que más cómodo nos sea, vi, nano, emacs... Lo importante sería que dicho script se llamase 2fa.sh y se alojase en /usr/bin. Además dicho script tendría que ser creado como root. Nuestro comienzo sería el siguiente:

  1. #!/bin/bash
  2. #VARIABLES
  3. CLAVE=""
  4. #GENERADOR NUMERO ALEATORIO
  5. function generador_clave()
  6. {
  7.         for i in `seq 1 6`;
  8.         do
  9.                 rand=$(( $RANDOM % 10 ))
  10.                 CLAVE="${CLAVE}${rand}"
  11.         done
  12. }
  13. generador_clave

Este comienzo como podéis ver es tan simple como crear una variable "vacía" llamada CLAVE. Luego crearíamos una función que asignaría 6 dígitos aleatorios a dicha variable y por último llamaríamos a la función. Algo muy sencillo y que de momento no entraña ninguna dificultad...

Esta clave sería la que luego tendríamos que usarla para autenticarnos con lo que es importante hacérnosla llegar... Aquí podemos pensar en diferentes métodos; algunos más laboriosos que otros, tales como el uso de un router GSM que nos envíe mensajes a nuestro móvil... Pero en este caso lo que queremos es una opción sencilla y que no requiera ningún tipo de inversión o de aparato "especial", con lo que en mi caso en particular me he decantado por enviar la clave por correo electrónico. Para ello habría que usar la aplicación sendemail; aplicación que no viene instalada por defecto en el sistema con lo que en caso de no tenerla instalada habría que instalarla vía consola mediante el comando:

apt-get install sendemail

Con esta aplicación instalada, antes de usarla en nuestro script es importante entender su funcionamiento... Esta aplicación lo que hace es que nos conectemos a una cuenta de correo mediante el protocolo SMTP, para después desde dicha cuenta de correo enviar una mail a un destinatario; acción para lo cual se usaría la siguiente sintaxis:

sendemail -f mail_origen -t mail_destino -xu "mail_origen" -xp "contraseña" -m "Contenido_mensaje" -s servidor_smtp:puerto -o tls=yes -u "Título_mail" 

Aquí lo único "especial" sería el servidor SMTP y el puerto a usar, ya que ambos datos variarían dependiendo de si el servidor es de un dominio u otro. Los correos más populares serían gmail y hotmail así que he aquí a continuación la lista de servidores SMTP de dichos servicios de correo.

Hotmail

Servidor: smtp.live.com
Puerto: 25

Gmail

Servidor: smtp.gmail.com
Puerto: 465

A sabiendas del comando y de la información de los servidores SMTP, podemos ahora darle más forma a nuestro script y darle la funcionalidad de enviarnos por correo la clave previamente generada, haciendo que tome el siguiente aspecto:

  1. #!/bin/bash
  2. #VARIABLES
  3. CLAVE=""
  4. #ACTIVACION TRAP
  5. trap '' 2 3 9 15
  6. #GENERADOR NUMERO ALEATORIO
  7. function generador_clave()
  8. {
  9.         for i in `seq 1 6`;
  10.         do
  11.                 rand=$(( $RANDOM % 10 ))
  12.                 CLAVE="${CLAVE}${rand}"
  13.         done
  14. }
  15. #ENVIADOR CORREO
  16. function enviador_mail()
  17. {
  18.     sendemail -f usuario@gmail.com -t destino@hotmail.com -xu "usuario@gmail.com" -xp "Contraseña" -m "${CLAVE}" -s smtp.gmail.com:465 -o tls=yes -u "2FA" > /dev/null
  19. }
  20. #LLAMADA FUNCIONES
  21. generador_clave
  22. enviador_mail

Con lo que tenemos ahora ya estaríamos mandando una clave aleatoria de 6 dígitos desde un correo de origen a un destino... Lo cual esta muy bien y se va acercando a lo que buscamos, pero que todavía no se trata de lo que buscamos, pues no se hace nada con dicha clave...

Ahora llegaría el turno de hacer que el usuario pueda introducir una clave manualmente y que ésta sea comparada con la clave que hemos enviado por correo. Aquí podemos optar por diferentes métodos de introducción de contraseña dependiendo de cómo nos logueemos; no es lo mismo loguearnos por consola que vía entorno gráfico, con lo que tendremos que hacer un script que se pueda adaptar a la situación dependiendo de nuestras necesidades. Para este script en concreto haremos que se pueda introducir la clave tanto por consola, como en entornos gráficos Gnome y KDE. Eso significaría que usaríamos 3 métodos de introducción de datos distintos:

  • Usaríamos el comando read para leer el dato introducido desde la consola. El comando sería read PASS (PASS el nombre de la variable que usaríamos para almacenar la contraseña).
  • Para la introducción de la contraseña mediante una interfaz gráfica, primero habría que ver si el entorno gráfico es gnome o KDE. En caso de ser KDE habría que usar la herramienta kdialog que usaría la sintaxis:
PASS=$(kdialog --title "PASSWORD" --inputbox "Introduce la contraseña")
  • Mientras que en Gnome la herramienta sería zenity, y la sintaxis sería esta:
PASS=$(zenity --entry --title "PASSWORD" --text "Introduce la contraseña")

Con estas introducciones haríamos que la variable PASS tuviese el valor introducido; variable que compararíamos con la clave. Obviamente haríamos que estas introducciones apareciesen constantemente hasta que la clave introducida coincida con la clave.

Con lo que recién acabamos de comentar, el script tomaría el siguiente aspecto, el cual como podéis ver, es mucho más completo. Un script que con lo que aparece a continuación ya sería funcional:

  1. #!/bin/bash
  2. #VARIABLES
  3. CLAVE=""
  4. PASS=""
  5. #GENERADOR NUMERO ALEATORIO
  6. function generador_clave()
  7. {
  8.         for i in `seq 1 6`;
  9.         do
  10.                 rand=$(( $RANDOM % 10 ))
  11.                 CLAVE="${CLAVE}${rand}"
  12.         done
  13. }
  14. #ENVIADOR CORREO
  15. function enviador_mail()
  16. {
  17.     sendemail -f usuario@gmail.com -t destino@hotmail.com -xu "usuario@gmail.com" -xp "Contraseña" -m "${CLAVE}" -s smtp.gmail.com:465 -o tls=yes -u "2FA" > /dev/null
  18. }
  19. #LLAMADA FUNCIONES
  20. generador_clave
  21. enviador_mail
  22. #DIALOGS PARA ENTORNO GRAFICO
  23. if [[ "$(env |grep DISPLAY)" = "DISPLAY=:0" ]]then
  24.     while [ "${CLAVE}" !"${PASS}" ]do
  25.         if [[ "$(env |grep XDG_CURRENT_DESKTOP)" = "KDE" ]]then #DIALOG PARA KDE
  26.             PASS=$(kdialog --title "PASSWORD" --inputbox "Introduce la contraseña")
  27.         else #DIALOG PARA GNOME
  28.             PASS=$(zenity --entry --title="PASSWORD" --text="Introduce la contraseña")
  29.         fi
  30.     done
  31. #TEXTO PLANO PARA CONSOLA
  32. else
  33.     while [ "${CLAVE}" !"${PASS}" ]do
  34.         echo 'Introduce la contraseña'
  35.         read PASS
  36.     done
  37. fi

Gracias a este script compararíamos la contraseña introducida (ya sea por consola o por interfaz gráfica) y haríamos que no dejase de pedir la contraseña hasta que sea la correcta... Es decir que el script en sí cumpliría su función, si bien todavía habría que mejorarlo un poco más, pues tiene pequeñas carencias...

La primera de ellas sería que cada vez que un usuario se loguee para lo que sea, pedirán el segundo factor de autenticación, aún cuando sea el mismo y simplemente haya abierto otra consola... Lo suyo sería que durante el tiempo que el equipo estuviese encendido, no volviese a pedir el segundo factor de autenticación, pues sino a la mínima tendríamos al sistema pidiéndonos una nueva clave de forma innecesaria, pues se da por hecho que si has pasado exitosamente la primera vez por dicho método, no hay necesidad de repetir dicha autenticación. Para ello simplemente habría que crear un fichero en el directorio temporal /tmp (que sabemos que vacía su contenido al reiniciar el equipo) cuya existencia se comprobaría dentro del fichero .bashrc para así saber si ejecutar el script o no. En mi caso he denominado a estos ficheros temporales token_${UID}, donde $UID sería el identificador del usuario. Por defecto dicho fichero no existiría, con lo que la primera vez siempre habría que crear dicho fichero mediante el script. Es por eso que en el fichero .bashrc haríamos que el script se ejecutase si el fichero NO existiese, lo cual ser haría modificando la última línea del dicho fichero para que tenga este aspecto:

  1. if [ ! -f /tmp/token_${UID}] ; then
  2.     /usr/bin/2fa.sh
  3. fi

Además, en el estado actual el script es bastante vulnerable; un simple ctrl + c en un login por consola lo detendría y haría que se saltasen este paso sin problemas... Es por ello que además del cambio atrás mencionado vamos a "atrapar" algunas señales para evitar esta situación mediante el comando trap; en concreto las señales 2 3 9 y 15. Obviamente, al finalizar el script deshabilitaríamos dicho "atrape".

Ambas modificaciones, tanto la evasión de la repetición, como la captura de ciertas señales harían que el script 2fa.sh tomase este aspecto:

  1. #!/bin/bash
  2. #VARIABLES
  3. CLAVE=""
  4. PASS=""
  5. #ACTIVACION TRAP
  6. trap '' 2 3 9 15
  7. #GENERADOR NUMERO ALEATORIO
  8. function generador_clave()
  9. {
  10.         for i in `seq 1 6`;
  11.         do
  12.                 rand=$(( $RANDOM % 10 ))
  13.                 CLAVE="${CLAVE}${rand}"
  14.         done
  15. }
  16. #ENVIADOR CORREO
  17. function enviador_mail()
  18. {
  19.     sendemail -f usuario@gmail.com -t destino@hotmail.com -xu "usuario@gmail.com" -xp "Contraseña" -m "${CLAVE}" -s smtp.gmail.com:465 -o tls=yes -u "2FA" > /dev/null
  20. }
  21. #LLAMADA FUNCIONES
  22. generador_clave
  23. enviador_mail
  24. #DIALOGS PARA ENTORNO GRAFICO
  25. if [[ "$(env |grep DISPLAY)" = "DISPLAY=:0" ]]then
  26.     while [ "${CLAVE}" !"${PASS}" ]do
  27.         if [[ "$(env |grep XDG_CURRENT_DESKTOP)" = "KDE" ]]then #DIALOG PARA KDE
  28.             PASS=$(kdialog --title "PASSWORD" --inputbox "Introduce la contraseña")
  29.         else #DIALOG PARA GNOME
  30.             PASS=$(zenity --entry --title="PASSWORD" --text="Introduce la contraseña")
  31.         fi
  32.     done
  33. #TEXTO PLANO PARA CONSOLA
  34. else
  35.     while [ "${CLAVE}" !"${PASS}" ]do
  36.         echo 'Introduce la contraseña'
  37.         read PASS
  38.     done
  39. fi
  40. #EVITAR REPETICION 2FA PARA UN MISMO USUARIO HASTA REINICIO
  41. if [ "${CLAVE}" = "${PASS}" ]then
  42.     touch /tmp/token_${UID}
  43.     chmod 400 /tmp/token_${UID}
  44. fi
  45. #DESACTIVACION TRAP
  46. trap - 2 3 9 15

Ahora sí que tendríamos un script que no solo es funcional, sino que también puede usarse en situaciones reales... Un script que añadiría una capa de seguridad adicional a nuestro sistema y que únicamente requiere añadir la llamada al script /usr/bin/2fa.sh en el fichero .bashrc de cada usuario que queremos que tenga doble seguridad. Solo quedaría por hacer una cosa más en nuestro script, que sería opcional y dependería de si tenemos varios usuarios usando el mismo equipo (con diferentes cuentas de usuario, se entiende). Este script requiere especificar a mano todos los datos de mail, el origen, el destino, usuario, contraseña... Al ser un binario que va a ser usado por todos los usuarios, tendríamos que ver si nos merece la pena o no poner o no diferentes configuraciones de correo dependiendo del usuario...

En caso de querer tener flexibilidad con dicho dato, el cual es bastante personal y que no queremos que el resto de usuarios vea... Podemos hacer que cada usuario tenga una configuración especial y que en caso de que no haya configuración especial alguna para dicho usuario, se use un correo genérico. Esto se podría lograr gracias a la privacidad que nos ofrece la carpeta home. Cada usuario tiene una carpeta /home/nombre_usuario a la que solamente pueden acceder ellos y root con lo que podemos crear un fichero con un nombre y una estructura determinada que sirva para extraer los datos de allí y usarlos. Por ejemplo podríamos crear un fichero llamado llamado User2fa dentro de nuestro directorio home (por ejemplo dentro de /home/ivan en mi caso) que tuviese la estructura:

  1. DE: usuario@gmail.com
  2. A: destino@hotmail.com
  3. USUARIO: "usuario@gmail.com"
  4. PWD: "Contraseña"
  5. SERVIDOR: smtp.gmail.com
  6. PUERTO: 465

Con estos datos, simplemente habría que recorrer el fichero y recoger los valores para luego ponerlos en la posición indicada del script... Algo tan sencillo como editar la función enviador_mail y hacer que el aspecto final sea este:

  1. #!/bin/bash
  2. #VARIABLES
  3. CLAVE=""
  4. PASS=""
  5. #ACTIVACION TRAP
  6. trap '' 2 3 9 15
  7. #GENERADOR NUMERO ALEATORIO
  8. function generador_clave()
  9. {
  10.         for i in `seq 1 6`;
  11.         do
  12.                 rand=$(( $RANDOM % 10 ))
  13.                 CLAVE="${CLAVE}${rand}"
  14.         done
  15. }
  16. #ENVIADOR CORREO
  17. function enviador_mail()
  18. {
  19.     # SI EXISTE EL fichero User2fa, usamos los datos de este
  20.     if [ -f ~/User2fa ]then
  21.         i=1
  22.         while [ ${i} -le 6 ]do
  23.             VAR[${i}]="$(head -n ${i} ~/User2fa |tail -n -1 |awk '{print $2}')"
  24.             i=$(($i + 1))
  25.         done
  26.         sendemail -f ${VAR[1]} -t ${VAR[2]} -xu "${VAR[3]}" -xp "${VAR[4]}" -m "${CLAVE}" -s ${VAR[5]}:${VAR[6]} -o tls=yes -u "2FA" >/dev/null
  27.     else # EN CASO CONTRARIO, USAMOS UNOS DATOS GENERICOS
  28.         sendemail -f usuario@gmail.com -t destino@hotmail.com -xu "usuario@gmail.com" -xp "Contraseña" -m "${CLAVE}" -s smtp.gmail.com:465 -otls=yes -u "2FA" > /dev/null
  29.     fi
  30. }
  31. #LLAMADA FUNCIONES
  32. generador_clave
  33. enviador_mail
  34. #DIALOGS PARA ENTORNO GRAFICO
  35. if [[ "$(env |grep DISPLAY)" = "DISPLAY=:0" ]]then
  36.     while [ "${CLAVE}" !"${PASS}" ]do
  37.         if [[ "$(env |grep XDG_CURRENT_DESKTOP)" = "KDE" ]]then #DIALOG PARA KDE
  38.             PASS=$(kdialog --title "PASSWORD" --inputbox "Introduce la contraseña")
  39.         else #DIALOG PARA GNOME
  40.             PASS=$(zenity --entry --title="PASSWORD" --text="Introduce la contraseña")
  41.         fi
  42.     done
  43. #TEXTO PLANO PARA CONSOLA
  44. else
  45.     while [ "${CLAVE}" !"${PASS}" ]do
  46.         echo 'Introduce la contraseña'
  47.         read PASS
  48.     done
  49. fi
  50. #EVITAR REPETICION 2FA PARA UN MISMO USUARIO HASTA REINICIO
  51. if [ "${CLAVE}" = "${PASS}" ]then
  52.     touch /tmp/token_${UID}
  53.     chmod 400 /tmp/token_${UID}
  54. fi
  55. #DESACTIVACION TRAP
  56. trap - 2 3 9 15

Gracias a estos cambios finales realizados no solo tendríamos un segundo factor de autenticación sino que además tendríamos unos datos de mail adaptados a las necesidades de cada usuario, ofreciendo la flexibilidad de que cada usuario que use el segundo factor de autenticación pueda escoger el mail de origen y de destino a su completo gusto.

Espero que os haya resultado útil.

Saludos.