Seguridad con Go parte II: Eficiencia y fiabilidad en la era digital

Seguridad con Go parte II: Eficiencia y fiabilidad en la era digital

En la primera parte de nuestro tema, dimos una breve introducción sobre Go y cómo se utiliza en la seguridad informática, además de realizar algunos ejercicios básicos. En esta segunda parte, trataré de ir un poco más lejos mediante ejercicios prácticos, los cuales solo son con fines educativos.

Aclaro que, para esto, uso el sistema operativo Kali Linux, lo cual es recomendable gracias a que posee algunas herramientas y dependencias preinstaladas para el desarrollo de nuestras herramientas en Go.

1. Escáner de Puertos TCP

Uno de los detalles más comunes al realizar auditorías u otro tipo de actividad dentro de la seguridad informática es la escanear a nuestro objetivo o máquina.

Debido a mi trabajo, he realizado algunas auditorías para algunos clientes e infraestructuras, lo que me permite decirte que si apenas comienzas en la seguridad informática, siempre y por obligación deberás realizar un escaneo a la máquina u objetivo, esto con el fin de encontrar puertos abiertos a los cuales posteriormente les pueden realizar pruebas y ver si tienen alguna vulnerabilidad. Dicho lo anterior, procedemos con las instrucciones para crear nuestro escáner de puertos TCP.

Abrimos nuestro editor de código (en este caso, usaré Vim mediante la terminal de Kali Linux) y creamos nuestro archivo con el siguiente comando:

Figura 1. Crear archivo Go. Fuente: Bladimir Peláez.

Figura 2. Librerías básicas. Fuente: Bladimir Peláez.


Como se puede apreciar en la figura, necesitamos importar algunas librerías para que pueda funcionar nuestro escáner. De lo contrario, nos arrojará algunos errores al ejecutar nuestro programa en Go. Antes de continuar, déjame explicarte un poco sobre estas librerías.

  • fmt: proporciona funciones para formatear texto de salida y entrada. Es ampliamente utilizado para imprimir mensajes en la consola y formatear cadenas de texto.
  • net: es fundamental para la programación de redes en Go. Proporciona funciones y tipos de datos para trabajar con conexiones de red, como conexiones TCP/IP y UDP.
  • strconv: se utiliza para realizar conversiones entre cadenas de caracteres y tipos de datos numéricos, como enteros y flotantes.
  • time: su mismo nombre dice su función, pues básicamente proporciona funcionalidad para trabajar con el tiempo y el manejo de fechas y horas en Go.

Ahora podemos continuar agregando la función principal a nuestro programa.

Figura 3. Función principal. Fuente: Bladimir Peláez.


Como puedes ver, he definido la función principal de nuestro programa. Esta función hará el proceso de escaneo según el sitio o url dada. Resalto que uso solamente un sitio de prueba. Ahora déjame darte una breve explicación de lo que acontece en esta función.

  • func main es para decirle a Go que es una función.
  • target := "testhtml5.vulnweb.com": define la variable target que almacena el nombre de nuestro objetivo al cual se realizará el escaneo.
  • for port := 1; port <= 1024; port++: inicia un loop for que recorre los puertos del 1 al 1024, de tipo TCP y bien conocidos.
  • address := target + ":" + strconv.Itoa(port): básicamente crea una cadena que combina el nombre del servidor con el número del puerto actual.
  • conn, err := net.DialTimeout("tcp", address, time.Second): intenta establecer una conexión TCP con el servidor y utiliza un tiempo de espera de un segundo.
  • if err != nil { continue }: verifica si se produjo un error al intentar la conexión. Si se produce un error (lo que indica que el puerto está cerrado o no responde), el bucle continúa con el siguiente puerto sin mostrar un mensaje.
  • defer conn.Close(): Si la conexión se establece con éxito, se programa el cierre de la conexión al final de la función.
  • fmt.Printf("Puerto %d abierto\n", port): si la conexión se establece correctamente, se muestra un mensaje indicando que el puerto está abierto en el número de puerto actual.
Figura 4. Escaner TCP completo con Go. Fuente: Bladimir Peláez.


Explicado lo anterior y como podemos ver en la imagen, tenemos nuestro programa en Go totalmente listo. Ahora lo ejecutamos en la terminal para ver el resultado.

Figura 5. Realizando el escaneo. Fuente: Bladimir Peláez.


Hemos iniciado nuestro programa con el comando go run escaner.go. Hecho esto, esperamos hasta que nuestro programa finalice y arroje resultados. En mi caso, utilicé un sitio web de prueba y, como puedes ver, me ha indicado que el puerto 80 está abierto en el sitio web.

En algunas ocasiones suele tardar un poco, todo depende de tu red y del sitio que quieras escanear. Recomiendo que, si quieres practicar, lo hagas de manera controlada, ya sea en entornos virtuales o en redes locales de tu propiedad.

2. Verificación básica de contraseñas

¿Alguna vez te has preguntado cuán seguras son las contraseñas que creas? Seguro sabes que es de suma importancia proteger nuestros datos e información, ¿verdad? En esta parte, te mostraré como usar Go para verificar la seguridad de las contraseñas y si estas cumplen con los estándares básicos recomendados.

Inicialmente crearé mi archivo llamado password.go. Recuerda que uso Vim por medio de la terminal, pero puedes usar el editor que gustes.

Figura 6. Archivo password.go. Fuente: Bladimir Peláez.


Hemos creado nuestro archivo e importado las librerías necesarias. Anteriormente, expliqué la función de la librería fmt y, en el caso de regexp, se usa para trabajar con expresiones regulares.

Figura 7. Función de entrada. Fuente: Bladimir Peláez.


Tenemos nuestra función principal definida, en la cual se realizará la mayor parte de la lógica del programa. Pero antes déjame explicarte un poco lo que contiene esta función.

  • password := "P@ssw0rd": aquí se almacenan los caracteres que queremos probar. En mi caso, he agregado un ejemplo de contraseña básico.
  • isStrong := isStrongPassword(password): en esta línea llamamos a una función que vamos a definir luego y le pasaremos como parámetro nuestra variable password.
  • if isStrong { ... } else { ... }: aquí hacemos la comprobación de isStrong para determinar si la contraseña es fuerte o no.
Figura 8. Función isStrongPassword. Fuente: Bladimir Peláez.


Esta función básicamente recibe un valor como argumento. Aquí recibe el valor password de la función main definida previamente y devuelve un valor booleano true o false que indica si la contraseña cumple o no con ciertos criterios de seguridad. Para entender un poco más sobre esto, y como he hecho antes, te explicaré un poco de lo que pasa en cada línea de este código.

  • if len(password) < 8 { return false }: aquí se necesita verificar si la contraseña tiene al menos 8 caracteres. De lo contrario, nos devolverá false.
  • if matched, _ := regexp.MatchString([A-Z], password); !matched { return false }: se utiliza una expresión regular para verificar si la contraseña contiene, al menos, una letra mayúscula. Si no la contiene, se devuelve false.
  • if matched, _ := regexp.MatchString([0-9], password); !matched { return false }: igual a la verificación anterior, se utiliza una expresión regular, pero en este caso para verificar si la contraseña contiene al menos un número.

Finalmente, si la contraseña pasa todas estas comprobaciones se devuelve true, lo que indica que cumple con los criterios de seguridad.

Ahora ejecutamos nuestro programa con el comando go run password.go:

Figura 9. Ejecución de password.go. Fuente: Bladimir Peláez.

Al ejecutar nuestro programa, nos indica que nuestra contraseña es fuerte y segura. Te invito a que lo pruebes por ti mismo/a si tienes curiosidad, porque es importante verificar las contraseñas que creas y evitar que gente malintencionada las rompa si tienen muy poca seguridad.

En mi experiencia con algunas herramientas que descifran contraseñas, me he dado cuenta de que hay muchas personas que usan contraseñas con una longitud un poco más larga de lo normal y, aparentemente, creen que esa práctica es segura. Si de verdad quieres tener una contraseña confiable y segura, usa combinaciones de letras, números, mayúsculas y, sobre todo, algunos caracteres especiales, pues esto ayuda a que sea algo muy difícil de descifrar y puedas proteger mejor tus datos e información.

3. Generador de Contraseñas con Go

En esta última parte mostraré cómo usar Go para crear un generador de contraseñas seguras, algo muy útil a la hora de establecer una nueva contraseña. Supongamos que te darás de alta en un nuevo sitio donde, obviamente, te piden una contraseña. La mayoría de las personas usan las mismas contraseñas para todos los sitios a los cuales acceden y, en algunos casos, solo suelen cambiar un carácter.

Yo era de ésos y déjame decirte que lo hacía para no tener que escribirlas o guardarlas para cada sitio, pero me di cuenta del gran riesgo que esto representa, pues solo basta que alguien tenga tu contraseña e intente acceder a algunos sitios usando tu misma contraseña . A continuación, te mostraré cómo creé un generador de contraseñas seguras con Go.

Figura 10. Creación de genpassword.go. Fuente: Bladimir Peláez.

Figura 11. Importando librerías. Fuente: Bladimir Peláez.

En lo anterior podemos ver la creación de nuestro archivo y, posteriormente, la importación de las librerías, las cuales son necesarias para nuestro programa. Puedes notar que he llamado la librería fmt, algo necesario en casi todos los programas creados con Go. La librería math/rand sirve para generar números aleatorios y el paquete time ayuda con el manejo de tiempos (como ya indiqué).

Figura 12. Funciones de nuestro programa. Fuente: Bladimir Peláez.

He definido dos funciones: la primera es para generar la contraseña y la segunda es nuestra función principal o de entrada en donde se produce la lógica de nuestro programa. Ahora explicaré qué ves aquí.

  • func generateRandomPassword(length int) string: define la función que toma como argumento un entero que indica la longitud de la contraseña que se va a generar.
  • const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": variable que contiene todos los caracteres con los cuales se harán todas las combinaciones para nuestra contraseña.
  • seed := rand.NewSource(time.Now().UnixNano()): crea una fuente de números aleatorios rand.Source utilizando la marca de tiempo actual en nanosegundos time.Now().UnixNano() como semilla seed. Esto asegura que cada vez que ejecutes el programa, aparezca una contraseña diferente.
  • random := rand.New(seed): crea un generador de números aleatorios utilizando la semilla definida anteriormente.
  • password := make([]byte, length): crea un slice de bytes llamado password con la longitud especificada por el argumento length. Este slice se utilizará para almacenar la contraseña generada.
  • for i := range password { ... }: inicia un loop for que recorre cada índice del slice password.
  • password[i] = charset[random.Intn(len(charset))]: En cada iteración del bucle se selecciona aleatoriamente un carácter del conjunto charset utilizando el generador de números aleatorios random y se almacena en la posición correspondiente del slice password.
  • return string(password): devuelve la contraseña generada como una cadena de caracteres.

Ahora en la función main:

  • password := generateRandomPassword(12): llamamos a la función generateRandomPassword con un argumento de longitud de 12 caracteres y almacenamos la contraseña generada en la variable password.
  • fmt.Printf("Contraseña generada: %s\n", password): imprimimos la contraseña generada en la consola utilizando fmt.Printf. La contraseña se mostrará en la consola como resultado de la ejecución del programa.

Ejecutamos nuestro programa y podemos ver lo siguiente:

Figura 13. Ejecución de genpassword.go. Fuente: Bladimir Peláez.

En la figura notamos cómo al ejecutar múltiples veces nuestro programa, éste nos devuelve una cadena de caracteres totalmente diferente. Estas cadenas de caracteres las podemos usar como contraseñas para algunos sitios web y puedes sentirte confiado/a al usarlas, pues mi enfoque estuvo en realizar un programa que conserve las buenas prácticas sobre los criterios y protección de las contraseñas que son generadas.

Conclusión

Después de explorar más a fondo el papel de Go en la seguridad informática, estoy aún más impresionado por su eficiencia y confiabilidad en este campo crítico. La capacidad de Go para crear binarios altamente eficientes y optimizados es esencial en la creación de aplicaciones de seguridad que requieren respuestas rápidas y el manejo eficiente de recursos.

Además, su enfoque en la concurrencia permite el desarrollo de sistemas de detección y respuesta en tiempo real, una necesidad en la ciberseguridad moderna. Mi confianza en Go como herramienta esencial para mejorar la seguridad de los sistemas digitales solo ha crecido a medida que profundizo en sus capacidades y ventajas en este ámbito.

Con Go, estoy preparado para enfrentar los desafíos continuos y contribuir con un entorno digital más seguro y confiable.

Espero que esta guía haya sido de utilidad para ti. ¡Saludos!

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.